diff options
134 files changed, 2854 insertions, 2467 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 10a367fa9267..a1d1fa781096 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -970,6 +970,7 @@ package android.content.pm { method public boolean isSystemApp(); field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8 field public int privateFlags; + field public int targetSandboxVersion; } public class LauncherApps { @@ -1017,6 +1018,7 @@ package android.content.pm { field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 + field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000 field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6f3e89229e4c..6737972dc34e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5187,19 +5187,11 @@ public class Notification implements Parcelable bindHeaderChronometerAndTime(contentView, p); bindProfileBadge(contentView, p); bindAlertedIcon(contentView, p); - bindActivePermissions(contentView, p); bindFeedbackIcon(contentView, p); bindExpandButton(contentView, p); mN.mUsesStandardHeader = true; } - private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) { - int color = getNeutralColor(p); - contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP); - contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP); - contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP); - } - private void bindFeedbackIcon(RemoteViews contentView, StandardTemplateParams p) { int color = getNeutralColor(p); contentView.setDrawableTint(R.id.feedback, false, color, PorterDuff.Mode.SRC_ATOP); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 043953d1aabd..0c6810c07394 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1134,6 +1134,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ @SystemApi + @TestApi public int targetSandboxVersion; /** diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e08af5534afd..7b2955db3318 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3373,6 +3373,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5; /** diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index bf94670e1ca3..6136a80978b7 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -52,14 +52,12 @@ public class NotificationHeaderView extends ViewGroup { private View mHeaderText; private View mSecondaryHeaderText; private OnClickListener mExpandClickListener; - private OnClickListener mAppOpsListener; private OnClickListener mFeedbackListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); private LinearLayout mTransferChip; private NotificationExpandButton mExpandButton; private CachingIconView mIcon; private View mProfileBadge; - private View mAppOps; private View mFeedbackIcon; private boolean mExpanded; private boolean mShowExpandButtonAtEnd; @@ -117,7 +115,6 @@ public class NotificationHeaderView extends ViewGroup { mExpandButton = findViewById(com.android.internal.R.id.expand_button); mIcon = findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); - mAppOps = findViewById(com.android.internal.R.id.app_ops); mFeedbackIcon = findViewById(com.android.internal.R.id.feedback); } @@ -146,7 +143,6 @@ public class NotificationHeaderView extends ViewGroup { // Icons that should go at the end if ((child == mExpandButton && mShowExpandButtonAtEnd) || child == mProfileBadge - || child == mAppOps || child == mFeedbackIcon || child == mTransferChip) { iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth(); @@ -212,7 +208,6 @@ public class NotificationHeaderView extends ViewGroup { // Icons that should go at the end if ((child == mExpandButton && mShowExpandButtonAtEnd) || child == mProfileBadge - || child == mAppOps || child == mFeedbackIcon || child == mTransferChip) { if (end == getMeasuredWidth()) { @@ -282,7 +277,7 @@ public class NotificationHeaderView extends ViewGroup { } private void updateTouchListener() { - if (mExpandClickListener == null && mAppOpsListener == null && mFeedbackListener == null) { + if (mExpandClickListener == null && mFeedbackListener == null) { setOnTouchListener(null); return; } @@ -291,14 +286,6 @@ public class NotificationHeaderView extends ViewGroup { } /** - * Sets onclick listener for app ops icons. - */ - public void setAppOpsOnClickListener(OnClickListener l) { - mAppOpsListener = l; - updateTouchListener(); - } - - /** * Sets onclick listener for feedback icon. */ public void setFeedbackOnClickListener(OnClickListener l) { @@ -394,7 +381,6 @@ public class NotificationHeaderView extends ViewGroup { private final ArrayList<Rect> mTouchRects = new ArrayList<>(); private Rect mExpandButtonRect; - private Rect mAppOpsRect; private Rect mFeedbackRect; private int mTouchSlop; private boolean mTrackGesture; @@ -408,9 +394,7 @@ public class NotificationHeaderView extends ViewGroup { mTouchRects.clear(); addRectAroundView(mIcon); mExpandButtonRect = addRectAroundView(mExpandButton); - mAppOpsRect = addRectAroundView(mAppOps); mFeedbackRect = addRectAroundView(mFeedbackIcon); - setTouchDelegate(new TouchDelegate(mAppOpsRect, mAppOps)); addWidthRect(); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @@ -471,11 +455,7 @@ public class NotificationHeaderView extends ViewGroup { break; case MotionEvent.ACTION_UP: if (mTrackGesture) { - if (mAppOps.isVisibleToUser() && (mAppOpsRect.contains((int) x, (int) y) - || mAppOpsRect.contains((int) mDownX, (int) mDownY))) { - mAppOps.performClick(); - return true; - } else if (mFeedbackIcon.isVisibleToUser() + if (mFeedbackIcon.isVisibleToUser() && (mFeedbackRect.contains((int) x, (int) y)) || mFeedbackRect.contains((int) mDownX, (int) mDownY)) { mFeedbackIcon.performClick(); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 3332143251fc..289a36f5380d 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -168,8 +168,6 @@ public class ConversationLayout extends FrameLayout private int mFacePileProtectionWidthExpanded; private boolean mImportantConversation; private TextView mUnreadBadge; - private ViewGroup mAppOps; - private Rect mAppOpsTouchRect = new Rect(); private View mFeedbackIcon; private float mMinTouchSize; private Icon mConversationIcon; @@ -214,7 +212,6 @@ public class ConversationLayout extends FrameLayout mConversationIconView = findViewById(R.id.conversation_icon); mConversationIconContainer = findViewById(R.id.conversation_icon_container); mIcon = findViewById(R.id.icon); - mAppOps = findViewById(com.android.internal.R.id.app_ops); mFeedbackIcon = findViewById(com.android.internal.R.id.feedback); mMinTouchSize = 48 * getResources().getDisplayMetrics().density; mImportanceRingView = findViewById(R.id.conversation_icon_badge_ring); @@ -1174,43 +1171,6 @@ public class ConversationLayout extends FrameLayout }); } mTouchDelegate.clear(); - if (mAppOps.getWidth() > 0) { - - // Let's increase the touch size of the app ops view if it's here - mAppOpsTouchRect.set( - mAppOps.getLeft(), - mAppOps.getTop(), - mAppOps.getRight(), - mAppOps.getBottom()); - for (int i = 0; i < mAppOps.getChildCount(); i++) { - View child = mAppOps.getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - // Make sure each child has at least a minTouchSize touch target around it - float childTouchLeft = child.getLeft() + child.getWidth() / 2.0f - - mMinTouchSize / 2.0f; - float childTouchRight = childTouchLeft + mMinTouchSize; - mAppOpsTouchRect.left = (int) Math.min(mAppOpsTouchRect.left, - mAppOps.getLeft() + childTouchLeft); - mAppOpsTouchRect.right = (int) Math.max(mAppOpsTouchRect.right, - mAppOps.getLeft() + childTouchRight); - } - - // Increase the height - int heightIncrease = 0; - if (mAppOpsTouchRect.height() < mMinTouchSize) { - heightIncrease = (int) Math.ceil((mMinTouchSize - mAppOpsTouchRect.height()) - / 2.0f); - } - mAppOpsTouchRect.inset(0, -heightIncrease); - - getRelativeTouchRect(mAppOpsTouchRect, mAppOps); - - // Extend the size of the app opps to be at least 48dp - mTouchDelegate.add(new TouchDelegate(mAppOpsTouchRect, mAppOps)); - - } if (mFeedbackIcon.getVisibility() == VISIBLE) { updateFeedbackIconMargins(); float width = Math.max(mMinTouchSize, mFeedbackIcon.getWidth()); @@ -1240,13 +1200,7 @@ public class ConversationLayout extends FrameLayout private void updateFeedbackIconMargins() { MarginLayoutParams lp = (MarginLayoutParams) mFeedbackIcon.getLayoutParams(); - if (mAppOps.getWidth() == 0) { - lp.setMarginStart(mNotificationHeaderSeparatingMargin); - } else { - float width = Math.max(mMinTouchSize, mFeedbackIcon.getWidth()); - int horizontalMargin = (int) ((width - mFeedbackIcon.getWidth()) / 2); - lp.setMarginStart(horizontalMargin); - } + lp.setMarginStart(mNotificationHeaderSeparatingMargin); mFeedbackIcon.setLayoutParams(lp); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index c73441cbd4f9..f96ed36fcedd 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1790,8 +1790,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, #ifdef ANDROID_EXPERIMENTAL_MTE SetTagCheckingLevel(PR_MTE_TCF_SYNC); #endif - // TODO(pcc): Use SYNC here once the allocator supports it. - heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC; + heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; break; default: #ifdef ANDROID_EXPERIMENTAL_MTE diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index d22a19faa52b..9a1b592c895a 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -160,43 +160,6 @@ android:visibility="gone" android:contentDescription="@string/notification_work_profile_content_description" /> - <LinearLayout - android:id="@+id/app_ops" - android:layout_height="match_parent" - android:layout_width="wrap_content" - android:layout_marginStart="6dp" - android:background="?android:selectableItemBackgroundBorderless" - android:orientation="horizontal"> - <ImageView - android:id="@+id/camera" - android:layout_width="?attr/notificationHeaderIconSize" - android:layout_height="?attr/notificationHeaderIconSize" - android:src="@drawable/ic_camera" - android:visibility="gone" - android:focusable="false" - android:contentDescription="@string/notification_appops_camera_active" - /> - <ImageView - android:id="@+id/mic" - android:layout_width="?attr/notificationHeaderIconSize" - android:layout_height="?attr/notificationHeaderIconSize" - android:src="@drawable/ic_mic" - android:layout_marginStart="4dp" - android:visibility="gone" - android:focusable="false" - android:contentDescription="@string/notification_appops_microphone_active" - /> - <ImageView - android:id="@+id/overlay" - android:layout_width="?attr/notificationHeaderIconSize" - android:layout_height="?attr/notificationHeaderIconSize" - android:src="@drawable/ic_alert_window_layer" - android:layout_marginStart="4dp" - android:visibility="gone" - android:focusable="false" - android:contentDescription="@string/notification_appops_overlay_active" - /> - </LinearLayout> <include layout="@layout/notification_material_media_transfer_action" android:id="@+id/media_seamless" diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 2a96e0925428..51ad286558b0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -29,8 +29,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarDeviceProvisionedControllerImpl; import com.android.systemui.car.keyguard.CarKeyguardViewController; +import com.android.systemui.car.notification.NotificationShadeWindowControllerImpl; import com.android.systemui.car.statusbar.DozeServiceHost; -import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController; import com.android.systemui.car.volume.CarVolumeDialogComponent; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.qualifiers.Background; @@ -53,12 +53,12 @@ import com.android.systemui.stackdivider.DividerModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.ShadeControllerImpl; import com.android.systemui.statusbar.policy.BatteryController; @@ -209,6 +209,10 @@ abstract class CarSystemUIModule { CarKeyguardViewController carKeyguardViewController); @Binds + abstract NotificationShadeWindowController bindNotificationShadeController( + NotificationShadeWindowControllerImpl notificationPanelViewController); + + @Binds abstract DeviceProvisionedController bindDeviceProvisionedController( CarDeviceProvisionedControllerImpl deviceProvisionedController); @@ -217,9 +221,5 @@ abstract class CarSystemUIModule { CarDeviceProvisionedControllerImpl deviceProvisionedController); @Binds - abstract NotificationShadeWindowController bindNotificationShadeWindowController( - DummyNotificationShadeWindowController notificationShadeWindowController); - - @Binds abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost); } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java new file mode 100644 index 000000000000..0c064bd2379c --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationShadeWindowControllerImpl.java @@ -0,0 +1,46 @@ +/* + * 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 com.android.systemui.car.window.OverlayViewGlobalStateController; +import com.android.systemui.statusbar.NotificationShadeWindowController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** The automotive version of the notification shade window controller. */ +@Singleton +public class NotificationShadeWindowControllerImpl implements + NotificationShadeWindowController { + + private final OverlayViewGlobalStateController mController; + + @Inject + public NotificationShadeWindowControllerImpl(OverlayViewGlobalStateController controller) { + mController = controller; + } + + @Override + public void setForceDozeBrightness(boolean forceDozeBrightness) { + // No-op since dozing is not supported in Automotive devices. + } + + @Override + public void setNotificationShadeFocusable(boolean focusable) { + mController.setWindowFocusable(focusable); + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java deleted file mode 100644 index 13f2b7ed45db..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/DummyNotificationShadeWindowController.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.statusbar; - -import android.app.IActivityManager; -import android.content.Context; -import android.view.WindowManager; - -import com.android.systemui.car.window.SystemUIOverlayWindowController; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; -import com.android.systemui.statusbar.policy.ConfigurationController; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * A dummy implementation of {@link NotificationShadeWindowController}. - * - * TODO(b/155711562): This should be replaced with a longer term solution (i.e. separating - * {@link BiometricUnlockController} from the views it depends on). - */ -@Singleton -public class DummyNotificationShadeWindowController extends NotificationShadeWindowController { - private final SystemUIOverlayWindowController mOverlayWindowController; - - @Inject - public DummyNotificationShadeWindowController(Context context, - WindowManager windowManager, IActivityManager activityManager, - DozeParameters dozeParameters, - StatusBarStateController statusBarStateController, - ConfigurationController configurationController, - KeyguardViewMediator keyguardViewMediator, - KeyguardBypassController keyguardBypassController, - SysuiColorExtractor colorExtractor, - DumpManager dumpManager, - SystemUIOverlayWindowController overlayWindowController) { - super(context, windowManager, activityManager, dozeParameters, statusBarStateController, - configurationController, keyguardViewMediator, keyguardBypassController, - colorExtractor, dumpManager); - mOverlayWindowController = overlayWindowController; - } - - @Override - public void setForceDozeBrightness(boolean forceDozeBrightness) { - // No op. - } - - @Override - public void setNotificationShadeFocusable(boolean focusable) { - // The overlay window is the car sysui equivalent of the notification shade. - mOverlayWindowController.setWindowFocusable(focusable); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index b7ae3dca5c16..bc58bfc97718 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -189,10 +189,12 @@ public class WifiStatusTracker { } } updateStatusLabel(); + mCallback.run(); } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { // Default to -200 as its below WifiManager.MIN_RSSI. updateRssi(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)); updateStatusLabel(); + mCallback.run(); } } @@ -218,13 +220,15 @@ public class WifiStatusTracker { return; } NetworkCapabilities networkCapabilities; - final Network currentWifiNetwork = mWifiManager.getCurrentNetwork(); - if (currentWifiNetwork != null && currentWifiNetwork.equals(mDefaultNetwork)) { + isDefaultNetwork = false; + if (mDefaultNetworkCapabilities != null) { + isDefaultNetwork = mDefaultNetworkCapabilities.hasTransport( + NetworkCapabilities.TRANSPORT_WIFI); + } + if (isDefaultNetwork) { // Wifi is connected and the default network. - isDefaultNetwork = true; networkCapabilities = mDefaultNetworkCapabilities; } else { - isDefaultNetwork = false; networkCapabilities = mConnectivityManager.getNetworkCapabilities( mWifiManager.getCurrentNetwork()); } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java index d2112a0ce759..883f4de1149c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -75,11 +75,6 @@ public interface NotificationMenuRowPlugin extends Plugin { public MenuItem getLongpressMenuItem(Context context); /** - * @return the {@link MenuItem} to display when app ops icons are pressed. - */ - public MenuItem getAppOpsMenuItem(Context context); - - /** * @return the {@link MenuItem} to display when feedback icon is pressed. */ public MenuItem getFeedbackMenuItem(Context context); diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml index 5cb840f24c84..7451ba8e88a4 100644 --- a/packages/SystemUI/res/values/config_tv.xml +++ b/packages/SystemUI/res/values/config_tv.xml @@ -22,9 +22,4 @@ <!-- Whether to enable microphone disclosure indicator (com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar). --> <bool name="audio_recording_disclosure_enabled">true</bool> - - <!-- Whether the indicator should expand and show the recording application's label. - When disabled (false) the "minimized" indicator would appear on the screen whenever an - application is recording, but will not reveal to the user what application this is. --> - <bool name="audio_recording_disclosure_reveal_packages">false</bool> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 10f2069087d5..c04775ad0077 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -47,6 +47,8 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.PluginDependencyProvider; @@ -64,11 +66,11 @@ import com.android.systemui.shared.system.DevicePolicyManagerWrapper; import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.VibratorHelper; @@ -85,10 +87,8 @@ import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ManagedProfileController; -import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java index 2deeb1230f09..5f8815665d88 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java @@ -39,21 +39,15 @@ import javax.inject.Singleton; */ @Singleton public class ForegroundServiceController { - public static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA, - AppOpsManager.OP_SYSTEM_ALERT_WINDOW, - AppOpsManager.OP_RECORD_AUDIO, - AppOpsManager.OP_COARSE_LOCATION, - AppOpsManager.OP_FINE_LOCATION}; + public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW}; private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>(); private final Object mMutex = new Object(); - private final NotificationEntryManager mEntryManager; private final Handler mMainHandler; @Inject - public ForegroundServiceController(NotificationEntryManager entryManager, - AppOpsController appOpsController, @Main Handler mainHandler) { - mEntryManager = entryManager; + public ForegroundServiceController(AppOpsController appOpsController, + @Main Handler mainHandler) { mMainHandler = mainHandler; appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> { mMainHandler.post(() -> { @@ -87,19 +81,6 @@ public class ForegroundServiceController { } /** - * Returns the keys for notifications from this package using the standard template, - * if they exist. - */ - @Nullable - public ArraySet<String> getStandardLayoutKeys(int userId, String pkg) { - synchronized (mMutex) { - final ForegroundServicesUserState services = mUserServices.get(userId); - if (services == null) return null; - return services.getStandardLayoutKeys(pkg); - } - } - - /** * Gets active app ops for this user and package */ @Nullable @@ -140,31 +121,6 @@ public class ForegroundServiceController { userServices.removeOp(packageName, appOpCode); } } - - // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by - // AppOpsCoordinator - // Update appOps if there are associated pending or visible notifications - final Set<String> notificationKeys = getStandardLayoutKeys(userId, packageName); - if (notificationKeys != null) { - boolean changed = false; - for (String key : notificationKeys) { - final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key); - if (entry != null - && uid == entry.getSbn().getUid() - && packageName.equals(entry.getSbn().getPackageName())) { - synchronized (entry.mActiveAppOps) { - if (active) { - changed |= entry.mActiveAppOps.add(appOpCode); - } else { - changed |= entry.mActiveAppOps.remove(appOpCode); - } - } - } - } - if (changed) { - mEntryManager.updateNotifications("appOpChanged pkg=" + packageName); - } - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index bb445832da93..1515272569d6 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -172,24 +172,8 @@ public class ForegroundServiceNotificationListener { sbn.getPackageName(), sbn.getKey()); } } - tagAppOps(entry); return true; }, true /* create if not found */); } - - // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by - // AppOpsCoordinator - private void tagAppOps(NotificationEntry entry) { - final StatusBarNotification sbn = entry.getSbn(); - ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps( - sbn.getUserId(), - sbn.getPackageName()); - synchronized (entry.mActiveAppOps) { - entry.mActiveAppOps.clear(); - if (activeOps != null) { - entry.mActiveAppOps.addAll(activeOps); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 9c5f9fe8bcc0..9e9d85a7cd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -84,7 +84,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; -import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; @@ -96,6 +95,7 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.NotificationChannelHelper; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -109,7 +109,6 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index 10d301d0fa93..fcb215c9448c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -30,11 +30,11 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java index 251ce1304930..4a5d142c35e8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java @@ -43,6 +43,7 @@ import android.hardware.display.ColorDisplayManager; import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.media.MediaRouter2Manager; +import android.media.session.MediaSessionManager; import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; @@ -226,6 +227,11 @@ public class SystemServicesModule { } @Provides + static MediaSessionManager provideMediaSessionManager(Context context) { + return context.getSystemService(MediaSessionManager.class); + } + + @Provides @Singleton static NetworkScoreManager provideNetworkScoreManager(Context context) { return context.getSystemService(NetworkScoreManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 56219c3356fc..b4e6e0ea37f4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -45,12 +45,14 @@ import com.android.systemui.stackdivider.DividerModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.DozeServiceHost; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.ShadeControllerImpl; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -172,5 +174,9 @@ public abstract class SystemUIDefaultModule { StatusBarKeyguardViewManager statusBarKeyguardViewManager); @Binds + abstract NotificationShadeWindowController bindNotificationShadeController( + NotificationShadeWindowControllerImpl notificationShadeWindowController); + + @Binds abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index ef51abb1404d..d213ac1132bb 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -132,7 +132,7 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.settings.CurrentUserContextTracker; import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.EmergencyDialerConstants; @@ -148,6 +148,7 @@ import java.util.Set; import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Provider; /** * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending @@ -401,7 +402,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (mDialog != null) { if (!mDialog.isShowingControls() && shouldShowControls()) { mDialog.showControls(mControlsUiControllerOptional.get()); - } else if (shouldShowLockMessage()) { + } else if (shouldShowLockMessage(mDialog)) { mDialog.showLockMessage(); } } @@ -698,19 +699,17 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mPowerAdapter = new MyPowerOptionsAdapter(); mDepthController.setShowingHomeControls(true); - GlobalActionsPanelPlugin.PanelViewController walletViewController = - getWalletViewController(); ControlsUiController uiController = null; if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) { uiController = mControlsUiControllerOptional.get(); } ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter, - walletViewController, mDepthController, mSysuiColorExtractor, + this::getWalletViewController, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, controlsAvailable(), uiController, mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); - if (shouldShowLockMessage()) { + if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); } dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. @@ -2124,7 +2123,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private MultiListLayout mGlobalActionsLayout; private Drawable mBackgroundDrawable; private final SysuiColorExtractor mColorExtractor; - private final GlobalActionsPanelPlugin.PanelViewController mWalletViewController; + private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory; + @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController; private boolean mKeyguardShowing; private boolean mShowing; private float mScrimAlpha; @@ -2144,7 +2144,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private TextView mLockMessage; ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter, - GlobalActionsPanelPlugin.PanelViewController walletViewController, + Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory, NotificationShadeDepthController depthController, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, @@ -2165,6 +2165,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mSysUiState = sysuiState; mOnRotateCallback = onRotateCallback; mKeyguardShowing = keyguardShowing; + mWalletFactory = walletFactory; // Window initialization Window window = getWindow(); @@ -2187,7 +2188,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, window.getAttributes().setFitInsetsTypes(0 /* types */); setTitle(R.string.global_actions); - mWalletViewController = walletViewController; initializeLayout(); } @@ -2200,8 +2200,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mControlsUiController.show(mControlsView, this::dismissForControlsActivity); } + private boolean isWalletViewAvailable() { + return mWalletViewController != null && mWalletViewController.getPanelContent() != null; + } + private void initializeWalletView() { - if (mWalletViewController == null || mWalletViewController.getPanelContent() == null) { + mWalletViewController = mWalletFactory.get(); + if (!isWalletViewAvailable()) { return; } @@ -2507,6 +2512,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private void dismissWallet() { if (mWalletViewController != null) { mWalletViewController.onDismissed(); + // The wallet controller should not be re-used after being dismissed. + mWalletViewController = null; } } @@ -2648,18 +2655,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, && !mControlsServiceInfos.isEmpty(); } - private boolean walletViewAvailable() { - GlobalActionsPanelPlugin.PanelViewController walletViewController = - getWalletViewController(); - return walletViewController != null && walletViewController.getPanelContent() != null; - } - - private boolean shouldShowLockMessage() { + private boolean shouldShowLockMessage(ActionsDialog dialog) { boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) == STRONG_AUTH_REQUIRED_AFTER_BOOT; return !mKeyguardStateController.isUnlocked() && (!mShowLockScreenCardsAndControls || isLockedAfterBoot) - && (controlsAvailable() || walletViewAvailable()); + && (controlsAvailable() || dialog.isWalletViewAvailable()); } private void onPowerMenuLockScreenSettingsChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index b993faabe9b1..b13be7b65312 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -42,7 +42,7 @@ class MediaCarouselController @Inject constructor( private val mediaHostStatesManager: MediaHostStatesManager, private val activityStarter: ActivityStarter, @Main executor: DelayableExecutor, - mediaManager: MediaDataFilter, + mediaManager: MediaDataManager, configurationController: ConfigurationController, falsingManager: FalsingManager ) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt index d0642ccf9714..aa3699e9a22b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt @@ -17,65 +17,48 @@ package com.android.systemui.media import javax.inject.Inject -import javax.inject.Singleton /** - * Combines updates from [MediaDataManager] with [MediaDeviceManager]. + * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */ -@Singleton -class MediaDataCombineLatest @Inject constructor( - private val dataSource: MediaDataManager, - private val deviceSource: MediaDeviceManager -) { +class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, + MediaDeviceManager.Listener { + private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf() - init { - dataSource.addListener(object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - if (oldKey != null && oldKey != key && entries.contains(oldKey)) { - entries[key] = data to entries.remove(oldKey)?.second - update(key, oldKey) - } else { - entries[key] = data to entries[key]?.second - update(key, key) - } - } - override fun onMediaDataRemoved(key: String) { - remove(key) - } - }) - deviceSource.addListener(object : MediaDeviceManager.Listener { - override fun onMediaDeviceChanged( - key: String, - oldKey: String?, - data: MediaDeviceData? - ) { - if (oldKey != null && oldKey != key && entries.contains(oldKey)) { - entries[key] = entries.remove(oldKey)?.first to data - update(key, oldKey) - } else { - entries[key] = entries[key]?.first to data - update(key, key) - } - } - override fun onKeyRemoved(key: String) { - remove(key) - } - }) + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (oldKey != null && oldKey != key && entries.contains(oldKey)) { + entries[key] = data to entries.remove(oldKey)?.second + update(key, oldKey) + } else { + entries[key] = data to entries[key]?.second + update(key, key) + } } - /** - * Get a map of all non-null data entries - */ - fun getData(): Map<String, MediaData> { - return entries.filter { - (key, pair) -> pair.first != null && pair.second != null - }.mapValues { - (key, pair) -> pair.first!!.copy(device = pair.second) + override fun onMediaDataRemoved(key: String) { + remove(key) + } + + override fun onMediaDeviceChanged( + key: String, + oldKey: String?, + data: MediaDeviceData? + ) { + if (oldKey != null && oldKey != key && entries.contains(oldKey)) { + entries[key] = entries.remove(oldKey)?.first to data + update(key, oldKey) + } else { + entries[key] = entries[key]?.first to data + update(key, key) } } + override fun onKeyRemoved(key: String) { + remove(key) + } + /** * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 24ca9708a4e3..0664a41f841d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -24,7 +24,6 @@ import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import java.util.concurrent.Executor import javax.inject.Inject -import javax.inject.Singleton private const val TAG = "MediaDataFilter" private const val DEBUG = true @@ -33,24 +32,24 @@ private const val DEBUG = true * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user * switches (removing entries for the previous user, adding back entries for the current user) * - * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from - * background users (e.g. timeouts) that UI classes should ignore. - * Instead, UI classes should listen to this so they can stay in sync with the current user. + * This is added at the end of the pipeline since we may still need to handle callbacks from + * background users (e.g. timeouts). */ -@Singleton class MediaDataFilter @Inject constructor( - private val dataSource: MediaDataCombineLatest, private val broadcastDispatcher: BroadcastDispatcher, private val mediaResumeListener: MediaResumeListener, - private val mediaDataManager: MediaDataManager, private val lockscreenUserManager: NotificationLockscreenUserManager, @Main private val executor: Executor ) : MediaDataManager.Listener { private val userTracker: CurrentUserTracker - private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() + private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() + internal val listeners: Set<MediaDataManager.Listener> + get() = _listeners.toSet() + internal lateinit var mediaDataManager: MediaDataManager - // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager - private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() + private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() + // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager + private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() init { userTracker = object : CurrentUserTracker(broadcastDispatcher) { @@ -60,31 +59,34 @@ class MediaDataFilter @Inject constructor( } } userTracker.startTracking() - dataSource.addListener(this) } override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (oldKey != null && oldKey != key) { + allEntries.remove(oldKey) + } + allEntries.put(key, data) + if (!lockscreenUserManager.isCurrentProfile(data.userId)) { return } - if (oldKey != null) { - mediaEntries.remove(oldKey) + if (oldKey != null && oldKey != key) { + userEntries.remove(oldKey) } - mediaEntries.put(key, data) + userEntries.put(key, data) // Notify listeners - val listenersCopy = listeners.toSet() - listenersCopy.forEach { + listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) } } override fun onMediaDataRemoved(key: String) { - mediaEntries.remove(key)?.let { + allEntries.remove(key) + userEntries.remove(key)?.let { // Only notify listeners if something actually changed - val listenersCopy = listeners.toSet() - listenersCopy.forEach { + listeners.forEach { it.onMediaDataRemoved(key) } } @@ -93,11 +95,11 @@ class MediaDataFilter @Inject constructor( @VisibleForTesting internal fun handleUserSwitched(id: Int) { // If the user changes, remove all current MediaData objects and inform listeners - val listenersCopy = listeners.toSet() - val keyCopy = mediaEntries.keys.toMutableList() + val listenersCopy = listeners + val keyCopy = userEntries.keys.toMutableList() // Clear the list first, to make sure callbacks from listeners if we have any entries // are up to date - mediaEntries.clear() + userEntries.clear() keyCopy.forEach { if (DEBUG) Log.d(TAG, "Removing $it after user change") listenersCopy.forEach { listener -> @@ -105,10 +107,10 @@ class MediaDataFilter @Inject constructor( } } - dataSource.getData().forEach { (key, data) -> + allEntries.forEach { (key, data) -> if (lockscreenUserManager.isCurrentProfile(data.userId)) { if (DEBUG) Log.d(TAG, "Re-adding $key after user change") - mediaEntries.put(key, data) + userEntries.put(key, data) listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) } @@ -121,7 +123,7 @@ class MediaDataFilter @Inject constructor( */ fun onSwipeToDismiss() { if (DEBUG) Log.d(TAG, "Media carousel swiped away") - val mediaKeys = mediaEntries.keys.toSet() + val mediaKeys = userEntries.keys.toSet() mediaKeys.forEach { mediaDataManager.setTimedOut(it, timedOut = true) } @@ -130,7 +132,7 @@ class MediaDataFilter @Inject constructor( /** * Are there any media notifications active? */ - fun hasActiveMedia() = mediaEntries.any { it.value.active } + fun hasActiveMedia() = userEntries.any { it.value.active } /** * Are there any media entries we should display? @@ -138,7 +140,7 @@ class MediaDataFilter @Inject constructor( * If resumption is disabled, we only want to show active players */ fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) { - mediaEntries.isNotEmpty() + userEntries.isNotEmpty() } else { hasActiveMedia() } @@ -146,10 +148,10 @@ class MediaDataFilter @Inject constructor( /** * Add a listener for filtered [MediaData] changes */ - fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener) + fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener) /** * Remove a listener that was registered with addListener */ - fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) -}
\ No newline at end of file + fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index bff334e92366..e239ba9b65fd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -101,12 +101,23 @@ class MediaDataManager( dumpManager: DumpManager, mediaTimeoutListener: MediaTimeoutListener, mediaResumeListener: MediaResumeListener, + mediaSessionBasedFilter: MediaSessionBasedFilter, + mediaDeviceManager: MediaDeviceManager, + mediaDataCombineLatest: MediaDataCombineLatest, + private val mediaDataFilter: MediaDataFilter, private val activityStarter: ActivityStarter, private var useMediaResumption: Boolean, private val useQsMediaPlayer: Boolean ) : Dumpable { - private val listeners: MutableSet<Listener> = mutableSetOf() + // Internal listeners are part of the internal pipeline. External listeners (those registered + // with [MediaDeviceManager.addListener]) receive events after they have propagated through + // the internal pipeline. + // Another way to think of the distinction between internal and external listeners is the + // following. Internal listeners are listeners that MediaDataManager depends on, and external + // listeners are listeners that depend on MediaDataManager. + // TODO(b/159539991#comment5): Move internal listeners to separate package. + private val internalListeners: MutableSet<Listener> = mutableSetOf() private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context) set(value) { @@ -130,9 +141,14 @@ class MediaDataManager( broadcastDispatcher: BroadcastDispatcher, mediaTimeoutListener: MediaTimeoutListener, mediaResumeListener: MediaResumeListener, + mediaSessionBasedFilter: MediaSessionBasedFilter, + mediaDeviceManager: MediaDeviceManager, + mediaDataCombineLatest: MediaDataCombineLatest, + mediaDataFilter: MediaDataFilter, activityStarter: ActivityStarter ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory, broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener, + mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter, activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context)) private val appChangeReceiver = object : BroadcastReceiver() { @@ -155,12 +171,26 @@ class MediaDataManager( init { dumpManager.registerDumpable(TAG, this) + + // Initialize the internal processing pipeline. The listeners at the front of the pipeline + // are set as internal listeners so that they receive events. From there, events are + // propagated through the pipeline. The end of the pipeline is currently mediaDataFilter, + // so it is responsible for dispatching events to external listeners. To achieve this, + // external listeners that are registered with [MediaDataManager.addListener] are actually + // registered as listeners to mediaDataFilter. + addInternalListener(mediaTimeoutListener) + addInternalListener(mediaResumeListener) + addInternalListener(mediaSessionBasedFilter) + mediaSessionBasedFilter.addListener(mediaDeviceManager) + mediaSessionBasedFilter.addListener(mediaDataCombineLatest) + mediaDeviceManager.addListener(mediaDataCombineLatest) + mediaDataCombineLatest.addListener(mediaDataFilter) + + // Set up links back into the pipeline for listeners that need to send events upstream. mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean -> setTimedOut(token, timedOut) } - addListener(mediaTimeoutListener) - mediaResumeListener.setManager(this) - addListener(mediaResumeListener) + mediaDataFilter.mediaDataManager = this val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED) broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL) @@ -198,10 +228,9 @@ class MediaDataManager( private fun removeAllForPackage(packageName: String) { Assert.isMainThread() - val listenersCopy = listeners.toSet() val toRemove = mediaEntries.filter { it.value.packageName == packageName } toRemove.forEach { - removeEntry(it.key, listenersCopy) + removeEntry(it.key) } } @@ -261,12 +290,45 @@ class MediaDataManager( /** * Add a listener for changes in this class */ - fun addListener(listener: Listener) = listeners.add(listener) + fun addListener(listener: Listener) { + // mediaDataFilter is the current end of the internal pipeline. Register external + // listeners as listeners to it. + mediaDataFilter.addListener(listener) + } /** * Remove a listener for changes in this class */ - fun removeListener(listener: Listener) = listeners.remove(listener) + fun removeListener(listener: Listener) { + // Since mediaDataFilter is the current end of the internal pipelie, external listeners + // have been registered to it. So, they need to be removed from it too. + mediaDataFilter.removeListener(listener) + } + + /** + * Add a listener for internal events. + */ + private fun addInternalListener(listener: Listener) = internalListeners.add(listener) + + /** + * Notify internal listeners of loaded event. + * + * External listeners registered with [addListener] will be notified after the event propagates + * through the internal listener pipeline. + */ + private fun notifyMediaDataLoaded(key: String, oldKey: String?, info: MediaData) { + internalListeners.forEach { it.onMediaDataLoaded(key, oldKey, info) } + } + + /** + * Notify internal listeners of removed event. + * + * External listeners registered with [addListener] will be notified after the event propagates + * through the internal listener pipeline. + */ + private fun notifyMediaDataRemoved(key: String) { + internalListeners.forEach { it.onMediaDataRemoved(key) } + } /** * Called whenever the player has been paused or stopped for a while, or swiped from QQS. @@ -284,16 +346,13 @@ class MediaDataManager( } } - private fun removeEntry(key: String, listenersCopy: Set<Listener>) { + private fun removeEntry(key: String) { mediaEntries.remove(key) - listenersCopy.forEach { - it.onMediaDataRemoved(key) - } + notifyMediaDataRemoved(key) } fun dismissMediaData(key: String, delay: Long) { - val listenersCopy = listeners.toSet() - foregroundExecutor.executeDelayed({ removeEntry(key, listenersCopy) }, delay) + foregroundExecutor.executeDelayed({ removeEntry(key) }, delay) } private fun loadMediaDataInBgForResumption( @@ -422,7 +481,7 @@ class MediaDataManager( val runnable = if (action.actionIntent != null) { Runnable { if (action.isAuthenticationRequired()) { - activityStarter.dismissKeyguardThenExecute ({ + activityStarter.dismissKeyguardThenExecute({ var result = sendPendingIntent(action.actionIntent) result }, {}, true) @@ -550,10 +609,7 @@ class MediaDataManager( if (mediaEntries.containsKey(key)) { // Otherwise this was removed already mediaEntries.put(key, data) - val listenersCopy = listeners.toSet() - listenersCopy.forEach { - it.onMediaDataLoaded(key, oldKey, data) - } + notifyMediaDataLoaded(key, oldKey, data) } } @@ -570,31 +626,21 @@ class MediaDataManager( val pkg = removed?.packageName val migrate = mediaEntries.put(pkg, updated) == null // Notify listeners of "new" controls when migrating or removed and update when not - val listenersCopy = listeners.toSet() if (migrate) { - listenersCopy.forEach { - it.onMediaDataLoaded(pkg, key, updated) - } + notifyMediaDataLoaded(pkg, key, updated) } else { // Since packageName is used for the key of the resumption controls, it is // possible that another notification has already been reused for the resumption // controls of this package. In this case, rather than renaming this player as // packageName, just remove it and then send a update to the existing resumption // controls. - listenersCopy.forEach { - it.onMediaDataRemoved(key) - } - listenersCopy.forEach { - it.onMediaDataLoaded(pkg, pkg, updated) - } + notifyMediaDataRemoved(key) + notifyMediaDataLoaded(pkg, pkg, updated) } return } if (removed != null) { - val listenersCopy = listeners.toSet() - listenersCopy.forEach { - it.onMediaDataRemoved(key) - } + notifyMediaDataRemoved(key) } } @@ -614,17 +660,31 @@ class MediaDataManager( if (!useMediaResumption) { // Remove any existing resume controls - val listenersCopy = listeners.toSet() val filtered = mediaEntries.filter { !it.value.active } filtered.forEach { mediaEntries.remove(it.key) - listenersCopy.forEach { listener -> - listener.onMediaDataRemoved(it.key) - } + notifyMediaDataRemoved(it.key) } } } + /** + * Invoked when the user has dismissed the media carousel + */ + fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss() + + /** + * Are there any media notifications active? + */ + fun hasActiveMedia() = mediaDataFilter.hasActiveMedia() + + /** + * Are there any media entries we should display? + * If resumption is enabled, this will include inactive players + * If resumption is disabled, we only want to show active players + */ + fun hasAnyMedia() = mediaDataFilter.hasAnyMedia() + interface Listener { /** @@ -644,7 +704,8 @@ class MediaDataManager( override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.apply { - println("listeners: $listeners") + println("internalListeners: $internalListeners") + println("externalListeners: ${mediaDataFilter.listeners}") println("mediaEntries: $mediaEntries") println("useMediaResumption: $useMediaResumption") println("appsBlockedFromResume: $appsBlockedFromResume") diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index ae7f66b5ac48..102a4842e3c3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -32,26 +32,23 @@ import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.Executor import javax.inject.Inject -import javax.inject.Singleton /** * Provides information about the route (ie. device) where playback is occurring. */ -@Singleton class MediaDeviceManager @Inject constructor( private val context: Context, private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, - private val mediaDataManager: MediaDataManager, - private val dumpManager: DumpManager + dumpManager: DumpManager ) : MediaDataManager.Listener, Dumpable { + private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Entry> = mutableMapOf() init { - mediaDataManager.addListener(this) dumpManager.registerDumpable(javaClass.name, this) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index 3598719fcb3a..ce184aa23a57 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -14,7 +14,7 @@ import javax.inject.Inject class MediaHost @Inject constructor( private val state: MediaHostStateHolder, private val mediaHierarchyManager: MediaHierarchyManager, - private val mediaDataFilter: MediaDataFilter, + private val mediaDataManager: MediaDataManager, private val mediaHostStatesManager: MediaHostStatesManager ) : MediaHostState by state { lateinit var hostView: UniqueObjectHostView @@ -79,12 +79,12 @@ class MediaHost @Inject constructor( // be a delay until the views and the controllers are initialized, leaving us // with either a blank view or the controllers not yet initialized and the // measuring wrong - mediaDataFilter.addListener(listener) + mediaDataManager.addListener(listener) updateViewVisibility() } override fun onViewDetachedFromWindow(v: View?) { - mediaDataFilter.removeListener(listener) + mediaDataManager.removeListener(listener) } }) @@ -113,9 +113,9 @@ class MediaHost @Inject constructor( private fun updateViewVisibility() { visible = if (showsOnlyActiveMedia) { - mediaDataFilter.hasActiveMedia() + mediaDataManager.hasActiveMedia() } else { - mediaDataFilter.hasAnyMedia() + mediaDataManager.hasAnyMedia() } val newVisibility = if (visible) View.VISIBLE else View.GONE if (newVisibility != hostView.visibility) { @@ -289,4 +289,4 @@ interface MediaHostState { * Get a copy of this view state, deepcopying all appropriate members */ fun copy(): MediaHostState -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt new file mode 100644 index 000000000000..f01713fb5f6c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt @@ -0,0 +1,163 @@ +/* + * 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.content.ComponentName +import android.content.Context +import android.media.session.MediaController +import android.media.session.MediaController.PlaybackInfo +import android.media.session.MediaSession +import android.media.session.MediaSessionManager +import android.util.Log +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins +import java.util.concurrent.Executor +import javax.inject.Inject + +private const val TAG = "MediaSessionBasedFilter" + +/** + * Filters media loaded events for local media sessions while an app is casting. + * + * When an app is casting there can be one remote media sessions and potentially more local media + * sessions. In this situation, there should only be a media object for the remote session. To + * achieve this, update events for the local session need to be filtered. + */ +class MediaSessionBasedFilter @Inject constructor( + context: Context, + private val sessionManager: MediaSessionManager, + @Main private val foregroundExecutor: Executor, + @Background private val backgroundExecutor: Executor +) : MediaDataManager.Listener { + + private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() + + // Keep track of MediaControllers for a given package to check if an app is casting and it + // filter loaded events for local sessions. + private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> = + LinkedHashMap() + + // Keep track of the key used for the session tokens. This information is used to know when + // dispatch a removed event so that a media object for a local session will be removed. + private val keyedTokens: MutableMap<String, MutableList<MediaSession.Token>> = mutableMapOf() + + private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener { + override fun onActiveSessionsChanged(controllers: List<MediaController>) { + handleControllersChanged(controllers) + } + } + + init { + backgroundExecutor.execute { + val name = ComponentName(context, NotificationListenerWithPlugins::class.java) + sessionManager.addOnActiveSessionsChangedListener(sessionListener, name) + handleControllersChanged(sessionManager.getActiveSessions(name)) + } + } + + /** + * Add a listener for filtered [MediaData] changes + */ + fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener) + + /** + * Remove a listener that was registered with addListener + */ + fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) + + /** + * May filter loaded events by not passing them along to listeners. + * + * If an app has only one session with playback type PLAYBACK_TYPE_REMOTE, then assuming that + * the app is casting. Sometimes apps will send redundant updates to a local session with + * playback type PLAYBACK_TYPE_LOCAL. These updates should be filtered to improve the usability + * of the media controls. + */ + override fun onMediaDataLoaded(key: String, oldKey: String?, info: MediaData) { + backgroundExecutor.execute { + val isMigration = oldKey != null && key != oldKey + if (isMigration) { + keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) } + } + if (info.token != null) { + keyedTokens.get(key)?.let { + tokens -> + tokens.add(info.token) + } ?: run { + val tokens = mutableListOf(info.token) + keyedTokens.put(key, tokens) + } + } + // Determine if an app is casting by checking if it has a session with playback type + // PLAYBACK_TYPE_REMOTE. + val remoteControllers = packageControllers.get(info.packageName)?.filter { + it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE + } + // Limiting search to only apps with a single remote session. + val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null + if (isMigration || remote == null || remote.sessionToken == info.token) { + // Not filtering in this case. Passing the event along to listeners. + dispatchMediaDataLoaded(key, oldKey, info) + } else { + // Filtering this event because the app is casting and the loaded events is for a + // local session. + Log.d(TAG, "filtering key=$key local=${info.token} remote=${remote?.sessionToken}") + // If the local session uses a different notification key, then lets go a step + // farther and dismiss the media data so that media controls for the local session + // don't hang around while casting. + if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) { + dispatchMediaDataRemoved(key) + } + } + } + } + + override fun onMediaDataRemoved(key: String) { + // Queue on background thread to ensure ordering of loaded and removed events is maintained. + backgroundExecutor.execute { + keyedTokens.remove(key) + dispatchMediaDataRemoved(key) + } + } + + private fun dispatchMediaDataLoaded(key: String, oldKey: String?, info: MediaData) { + foregroundExecutor.execute { + listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info) } + } + } + + private fun dispatchMediaDataRemoved(key: String) { + foregroundExecutor.execute { + listeners.toSet().forEach { it.onMediaDataRemoved(key) } + } + } + + private fun handleControllersChanged(controllers: List<MediaController>) { + packageControllers.clear() + controllers.forEach { + controller -> + packageControllers.get(controller.packageName)?.let { + tokens -> + tokens.add(controller) + } ?: run { + val tokens = mutableListOf(controller) + packageControllers.put(controller.packageName, tokens) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index ecdd7780a3f4..d22248924118 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -87,7 +87,7 @@ import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 739d30c2a707..c01bdc4c2f28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -60,7 +60,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.phone.StatusBar; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 0445c9879ac5..cdf1f104d310 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -38,7 +38,6 @@ import com.android.systemui.statusbar.notification.ActivityLaunchAnimator import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.phone.NotificationShadeWindowController import com.android.systemui.statusbar.phone.PanelExpansionListener import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.KeyguardStateController diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java new file mode 100644 index 000000000000..1fd0b03a77b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -0,0 +1,184 @@ +/* + * 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.statusbar; + +import android.view.ViewGroup; + +import androidx.annotation.Nullable; + +import com.android.systemui.statusbar.phone.StatusBarWindowCallback; + +import java.util.function.Consumer; + +/** + * Interface to control the state of the notification shade window. Not all methods of this + * interface will be used by each implementation of {@link NotificationShadeWindowController}. + */ +public interface NotificationShadeWindowController extends RemoteInputController.Callback { + + /** + * Registers a {@link StatusBarWindowCallback} to receive notifications about status bar + * window state changes. + */ + default void registerCallback(StatusBarWindowCallback callback) {} + + /** Notifies the registered {@link StatusBarWindowCallback} instances. */ + default void notifyStateChangedCallbacks() {} + + /** + * Registers a listener to monitor scrims visibility. + * + * @param listener A listener to monitor scrims visibility + */ + default void setScrimsVisibilityListener(Consumer<Integer> listener) {} + + /** + * Adds the notification shade view to the window manager. + */ + default void attach() {} + + /** Sets the notification shade view. */ + default void setNotificationShadeView(ViewGroup view) {} + + /** Gets the notification shade view. */ + @Nullable + default ViewGroup getNotificationShadeView() { + return null; + } + + /** Sets the state of whether the keyguard is currently showing or not. */ + default void setKeyguardShowing(boolean showing) {} + + /** Sets the state of whether the keyguard is currently occluded or not. */ + default void setKeyguardOccluded(boolean occluded) {} + + /** Sets the state of whether the keyguard is currently needs input or not. */ + default void setKeyguardNeedsInput(boolean needsInput) {} + + /** Sets the state of whether the notification shade panel is currently visible or not. */ + default void setPanelVisible(boolean visible) {} + + /** Sets the state of whether the notification shade is focusable or not. */ + default void setNotificationShadeFocusable(boolean focusable) {} + + /** Sets the state of whether the bouncer is showing or not. */ + default void setBouncerShowing(boolean showing) {} + + /** Sets the state of whether the backdrop is showing or not. */ + default void setBackdropShowing(boolean showing) {} + + /** Sets the state of whether the keyguard is fading away or not. */ + default void setKeyguardFadingAway(boolean keyguardFadingAway) {} + + /** Sets the state of whether the quick settings is expanded or not. */ + default void setQsExpanded(boolean expanded) {} + + /** Sets the state of whether the user activities are forced or not. */ + default void setForceUserActivity(boolean forceUserActivity) {} + + /** Sets the state of whether the user activities are forced or not. */ + default void setLaunchingActivity(boolean launching) {} + + /** Sets the state of whether the scrim is visible or not. */ + default void setScrimsVisibility(int scrimsVisibility) {} + + /** Sets the background blur radius of the notification shade window. */ + default void setBackgroundBlurRadius(int backgroundBlurRadius) {} + + /** Sets the state of whether heads up is showing or not. */ + default void setHeadsUpShowing(boolean showing) {} + + /** Sets whether the wallpaper supports ambient mode or not. */ + default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {} + + /** Gets whether the wallpaper is showing or not. */ + default boolean isShowingWallpaper() { + return false; + } + + /** Sets whether the window was collapsed by force or not. */ + default void setForceWindowCollapsed(boolean force) {} + + /** Sets whether panel is expanded or not. */ + default void setPanelExpanded(boolean isExpanded) {} + + /** Gets whether the panel is expanded or not. */ + default boolean getPanelExpanded() { + return false; + } + + /** Sets the state of whether the remote input is active or not. */ + default void onRemoteInputActive(boolean remoteInputActive) {} + + /** Sets the screen brightness level for when the device is dozing. */ + default void setDozeScreenBrightness(int value) {} + + /** + * Sets whether the screen brightness is forced to the value we use for doze mode by the status + * bar window. No-op if the device does not support dozing. + */ + default void setForceDozeBrightness(boolean forceDozeBrightness) {} + + /** Sets the state of whether sysui is dozing or not. */ + default void setDozing(boolean dozing) {} + + /** Sets the state of whether plugin open is forced or not. */ + default void setForcePluginOpen(boolean forcePluginOpen) {} + + /** Gets whether we are forcing plugin open or not. */ + default boolean getForcePluginOpen() { + return false; + } + + /** Sets the state of whether the notification shade is touchable or not. */ + default void setNotTouchable(boolean notTouchable) {} + + /** Sets a {@link OtherwisedCollapsedListener}. */ + default void setStateListener(OtherwisedCollapsedListener listener) {} + + /** Sets a {@link ForcePluginOpenListener}. */ + default void setForcePluginOpenListener(ForcePluginOpenListener listener) {} + + /** Sets whether the system is in a state where the keyguard is going away. */ + default void setKeyguardGoingAway(boolean goingAway) {} + + /** + * SystemUI may need top-ui to avoid jank when performing animations. After the + * animation is performed, the component should remove itself from the list of features that + * are forcing SystemUI to be top-ui. + */ + default void setRequestTopUi(boolean requestTopUi, String componentTag) {} + + /** + * Custom listener to pipe data back to plugins about whether or not the status bar would be + * collapsed if not for the plugin. + * TODO: Find cleaner way to do this. + */ + interface OtherwisedCollapsedListener { + void setWouldOtherwiseCollapse(boolean otherwiseCollapse); + } + + /** + * Listener to indicate forcePluginOpen has changed + */ + interface ForcePluginOpenListener { + /** + * Called when mState.forcePluginOpen is changed + */ + void onChange(boolean forceOpen); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 02a8feec3fa9..f272be477f51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -491,7 +491,6 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } } - row.showAppOpsIcons(entry.mActiveAppOps); row.showFeedbackIcon(mAssistantFeedbackController.showFeedbackIndicator(entry)); row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs()); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index 992f2da5c3b5..991b79aef93a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.AssistantFeedbackController; @@ -44,7 +45,6 @@ import com.android.systemui.statusbar.notification.collection.inflation.LowPrior import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.tracing.ProtoTracer; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java index 68ec6b620a53..2f1208879ebc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java @@ -82,14 +82,9 @@ public class AppOpsCoordinator implements Coordinator { // extend the lifetime of foreground notification services to show for at least 5 seconds mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender); - // listen for new notifications to add appOps - mNotifPipeline.addCollectionListener(mNotifCollectionListener); - // filter out foreground service notifications that aren't necessary anymore mNotifPipeline.addPreGroupFilter(mNotifFilter); - // when appOps change, update any relevant notifications to update appOps for - mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged); } public NotifSection getSection() { @@ -186,35 +181,6 @@ public class AppOpsCoordinator implements Coordinator { }; /** - * Adds appOps to incoming and updating notifications - */ - private NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { - @Override - public void onEntryAdded(NotificationEntry entry) { - tagAppOps(entry); - } - - @Override - public void onEntryUpdated(NotificationEntry entry) { - tagAppOps(entry); - } - - private void tagAppOps(NotificationEntry entry) { - final StatusBarNotification sbn = entry.getSbn(); - // note: requires that the ForegroundServiceController is updating their appOps first - ArraySet<Integer> activeOps = - mForegroundServiceController.getAppOps( - sbn.getUser().getIdentifier(), - sbn.getPackageName()); - - entry.mActiveAppOps.clear(); - if (activeOps != null) { - entry.mActiveAppOps.addAll(activeOps); - } - } - }; - - /** * Puts foreground service notifications into its own section. */ private final NotifSection mNotifSection = new NotifSection("ForegroundService") { @@ -230,53 +196,4 @@ public class AppOpsCoordinator implements Coordinator { return false; } }; - - private void onAppOpsChanged(int code, int uid, String packageName, boolean active) { - mMainExecutor.execute(() -> handleAppOpsChanged(code, uid, packageName, active)); - } - - /** - * Update the appOp for the posted notification associated with the current foreground service - * - * @param code code for appOp to add/remove - * @param uid of user the notification is sent to - * @param packageName package that created the notification - * @param active whether the appOpCode is active or not - */ - private void handleAppOpsChanged(int code, int uid, String packageName, boolean active) { - Assert.isMainThread(); - - int userId = UserHandle.getUserId(uid); - - // Update appOps of the app's posted notifications with standard layouts - final ArraySet<String> notifKeys = - mForegroundServiceController.getStandardLayoutKeys(userId, packageName); - if (notifKeys != null) { - boolean changed = false; - for (int i = 0; i < notifKeys.size(); i++) { - final NotificationEntry entry = findNotificationEntryWithKey(notifKeys.valueAt(i)); - if (entry != null - && uid == entry.getSbn().getUid() - && packageName.equals(entry.getSbn().getPackageName())) { - if (active) { - changed |= entry.mActiveAppOps.add(code); - } else { - changed |= entry.mActiveAppOps.remove(code); - } - } - } - if (changed) { - mNotifFilter.invalidateList(); - } - } - } - - private NotificationEntry findNotificationEntryWithKey(String key) { - for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) { - if (entry.getKey().equals(key)) { - return entry; - } - } - return null; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java deleted file mode 100644 index 28c53dc6d9b2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification.row; - -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.service.notification.StatusBarNotification; -import android.util.ArraySet; -import android.util.AttributeSet; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.R; - -/** - * The guts of a notification revealed when performing a long press. - */ -public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsContent { - private static final String TAG = "AppOpsGuts"; - - private PackageManager mPm; - - private String mPkg; - private String mAppName; - private int mAppUid; - private StatusBarNotification mSbn; - private ArraySet<Integer> mAppOps; - private MetricsLogger mMetricsLogger; - private OnSettingsClickListener mOnSettingsClickListener; - private NotificationGuts mGutsContainer; - private UiEventLogger mUiEventLogger; - - private OnClickListener mOnOk = v -> { - mGutsContainer.closeControls(v, false); - }; - - public AppOpsInfo(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public interface OnSettingsClickListener { - void onClick(View v, String pkg, int uid, ArraySet<Integer> ops); - } - - public void bindGuts(final PackageManager pm, - final OnSettingsClickListener onSettingsClick, - final StatusBarNotification sbn, - final UiEventLogger uiEventLogger, - ArraySet<Integer> activeOps) { - mPkg = sbn.getPackageName(); - mSbn = sbn; - mPm = pm; - mAppName = mPkg; - mOnSettingsClickListener = onSettingsClick; - mAppOps = activeOps; - mUiEventLogger = uiEventLogger; - - bindHeader(); - bindPrompt(); - bindButtons(); - - logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN); - mMetricsLogger = new MetricsLogger(); - mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, true); - } - - private void bindHeader() { - // Package name - Drawable pkgicon = null; - ApplicationInfo info; - try { - info = mPm.getApplicationInfo(mPkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - if (info != null) { - mAppUid = mSbn.getUid(); - mAppName = String.valueOf(mPm.getApplicationLabel(info)); - pkgicon = mPm.getApplicationIcon(info); - } - } catch (PackageManager.NameNotFoundException e) { - // app is gone, just show package name and generic icon - pkgicon = mPm.getDefaultActivityIcon(); - } - ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon); - ((TextView) findViewById(R.id.pkgname)).setText(mAppName); - } - - private void bindPrompt() { - final TextView prompt = findViewById(R.id.prompt); - prompt.setText(getPrompt()); - } - - private void bindButtons() { - View settings = findViewById(R.id.settings); - settings.setOnClickListener((View view) -> { - mOnSettingsClickListener.onClick(view, mPkg, mAppUid, mAppOps); - }); - TextView ok = findViewById(R.id.ok); - ok.setOnClickListener(mOnOk); - ok.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); - } - - private String getPrompt() { - if (mAppOps == null || mAppOps.size() == 0) { - return ""; - } else if (mAppOps.size() == 1) { - if (mAppOps.contains(AppOpsManager.OP_CAMERA)) { - return mContext.getString(R.string.appops_camera); - } else if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) { - return mContext.getString(R.string.appops_microphone); - } else { - return mContext.getString(R.string.appops_overlay); - } - } else if (mAppOps.size() == 2) { - if (mAppOps.contains(AppOpsManager.OP_CAMERA)) { - if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) { - return mContext.getString(R.string.appops_camera_mic); - } else { - return mContext.getString(R.string.appops_camera_overlay); - } - } else { - return mContext.getString(R.string.appops_mic_overlay); - } - } else { - return mContext.getString(R.string.appops_camera_mic_overlay); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (mGutsContainer != null && - event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - if (mGutsContainer.isExposed()) { - event.getText().add(mContext.getString( - R.string.notification_channel_controls_opened_accessibility, mAppName)); - } else { - event.getText().add(mContext.getString( - R.string.notification_channel_controls_closed_accessibility, mAppName)); - } - } - } - - @Override - public void setGutsParent(NotificationGuts guts) { - mGutsContainer = guts; - } - - @Override - public boolean willBeRemoved() { - return false; - } - - @Override - public boolean shouldBeSaved() { - return false; - } - - @Override - public boolean needsFalsingProtection() { - return false; - } - - @Override - public View getContentView() { - return this; - } - - @Override - public boolean handleCloseControls(boolean save, boolean force) { - logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_CLOSE); - if (mMetricsLogger != null) { - mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); - } - return false; - } - - @Override - public int getActualHeight() { - return getHeight(); - } - - private void logUiEvent(NotificationAppOpsEvent event) { - if (mSbn != null) { - mUiEventLogger.logWithInstanceId(event, - mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId()); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 22d0357b14c2..d7fa54f0091d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -239,7 +239,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mShowNoBackground; private ExpandableNotificationRow mNotificationParent; private OnExpandClickListener mOnExpandClickListener; - private View.OnClickListener mOnAppOpsClickListener; + private View.OnClickListener mOnAppClickListener; private View.OnClickListener mOnFeedbackClickListener; // Listener will be called when receiving a long click event. @@ -1139,7 +1139,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView items.add(NotificationMenuRow.createPartialConversationItem(mContext)); items.add(NotificationMenuRow.createInfoItem(mContext)); items.add(NotificationMenuRow.createSnoozeItem(mContext)); - items.add(NotificationMenuRow.createAppOpsItem(mContext)); mMenuRow.setMenuItems(items); } if (existed) { @@ -1598,7 +1597,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView RowContentBindStage rowContentBindStage, OnExpandClickListener onExpandClickListener, NotificationMediaManager notificationMediaManager, - CoordinateOnClickListener onAppOpsClickListener, CoordinateOnClickListener onFeedbackClickListener, FalsingManager falsingManager, StatusBarStateController statusBarStateController, @@ -1619,7 +1617,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mRowContentBindStage = rowContentBindStage; mOnExpandClickListener = onExpandClickListener; mMediaManager = notificationMediaManager; - setAppOpsOnClickListener(onAppOpsClickListener); setOnFeedbackClickListener(onFeedbackClickListener); mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; @@ -1677,14 +1674,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView requestLayout(); } - public void showAppOpsIcons(ArraySet<Integer> activeOps) { - if (mIsSummaryWithChildren) { - mChildrenContainer.showAppOpsIcons(activeOps); - } - mPrivateLayout.showAppOpsIcons(activeOps); - mPublicLayout.showAppOpsIcons(activeOps); - } - public void showFeedbackIcon(boolean show) { if (mIsSummaryWithChildren) { mChildrenContainer.showFeedbackIcon(show); @@ -1721,24 +1710,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); } - public View.OnClickListener getAppOpsOnClickListener() { - return mOnAppOpsClickListener; - } - - void setAppOpsOnClickListener(CoordinateOnClickListener l) { - mOnAppOpsClickListener = v -> { - createMenu(); - NotificationMenuRowPlugin provider = getProvider(); - if (provider == null) { - return; - } - MenuItem menuItem = provider.getAppOpsMenuItem(mContext); - if (menuItem != null) { - l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem); - } - }; - } - public View.OnClickListener getFeedbackOnClickListener() { return mOnFeedbackClickListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 1dbec6603f16..690dce915cf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -69,7 +69,6 @@ public class ExpandableNotificationRowController implements NodeController { private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; - private final ExpandableNotificationRow.CoordinateOnClickListener mOnAppOpsClickListener; private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener; private final NotificationGutsManager mNotificationGutsManager; private final OnDismissCallback mOnDismissCallback; @@ -110,7 +109,6 @@ public class ExpandableNotificationRowController implements NodeController { mStatusBarStateController = statusBarStateController; mNotificationGutsManager = notificationGutsManager; mOnDismissCallback = onDismissCallback; - mOnAppOpsClickListener = mNotificationGutsManager::openGuts; mOnFeedbackClickListener = mNotificationGutsManager::openGuts; mAllowLongPress = allowLongPress; mFalsingManager = falsingManager; @@ -132,7 +130,6 @@ public class ExpandableNotificationRowController implements NodeController { mRowContentBindStage, mOnExpandClickListener, mMediaManager, - mOnAppOpsClickListener, mOnFeedbackClickListener, mFalsingManager, mStatusBarStateController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 2986b9b75b98..c7e44c5e8e0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1568,18 +1568,6 @@ public class NotificationContentView extends FrameLayout { return header; } - public void showAppOpsIcons(ArraySet<Integer> activeOps) { - if (mContractedChild != null) { - mContractedWrapper.showAppOpsIcons(activeOps); - } - if (mExpandedChild != null) { - mExpandedWrapper.showAppOpsIcons(activeOps); - } - if (mHeadsUpChild != null) { - mHeadsUpWrapper.showAppOpsIcons(activeOps); - } - } - public void showFeedbackIcon(boolean show) { if (mContractedChild != null) { mContractedWrapper.showFeedbackIcon(show); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 729b131ac553..05d44f6eb406 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -268,8 +268,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx try { if (gutsView instanceof NotificationSnooze) { initializeSnoozeView(row, (NotificationSnooze) gutsView); - } else if (gutsView instanceof AppOpsInfo) { - initializeAppOpsInfo(row, (AppOpsInfo) gutsView); } else if (gutsView instanceof NotificationInfo) { initializeNotificationInfo(row, (NotificationInfo) gutsView); } else if (gutsView instanceof NotificationConversationInfo) { @@ -309,36 +307,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } /** - * Sets up the {@link AppOpsInfo} inside the notification row's guts. - * - * @param row view to set up the guts for - * @param appOpsInfoView view to set up/bind within {@code row} - */ - private void initializeAppOpsInfo( - final ExpandableNotificationRow row, - AppOpsInfo appOpsInfoView) { - NotificationGuts guts = row.getGuts(); - StatusBarNotification sbn = row.getEntry().getSbn(); - UserHandle userHandle = sbn.getUser(); - PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, - userHandle.getIdentifier()); - - AppOpsInfo.OnSettingsClickListener onSettingsClick = - (View v, String pkg, int uid, ArraySet<Integer> ops) -> { - mUiEventLogger.logWithInstanceId( - NotificationAppOpsEvent.NOTIFICATION_APP_OPS_SETTINGS_CLICK, - sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId()); - mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS); - guts.resetFalsingCheck(); - startAppOpsSettingsActivity(pkg, uid, ops, row); - }; - if (!row.getEntry().mActiveAppOps.isEmpty()) { - appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, mUiEventLogger, - row.getEntry().mActiveAppOps); - } - } - - /** * Sets up the {@link FeedbackInfo} inside the notification row's guts. * * @param row view to set up the guts for diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index d264af94947d..205cecc92e55 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -76,7 +76,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private Context mContext; private FrameLayout mMenuContainer; private NotificationMenuItem mInfoItem; - private MenuItem mAppOpsItem; private MenuItem mFeedbackItem; private MenuItem mSnoozeItem; private ArrayList<MenuItem> mLeftMenuItems; @@ -138,11 +137,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } @Override - public MenuItem getAppOpsMenuItem(Context context) { - return mAppOpsItem; - } - - @Override public MenuItem getFeedbackMenuItem(Context context) { return mFeedbackItem; } @@ -264,7 +258,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl // Only show snooze for non-foreground notifications, and if the setting is on mSnoozeItem = createSnoozeItem(mContext); } - mAppOpsItem = createAppOpsItem(mContext); mFeedbackItem = createFeedbackItem(mContext); NotificationEntry entry = mParent.getEntry(); int personNotifType = mPeopleNotificationIdentifier @@ -281,7 +274,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mRightMenuItems.add(mSnoozeItem); } mRightMenuItems.add(mInfoItem); - mRightMenuItems.add(mAppOpsItem); mRightMenuItems.add(mFeedbackItem); mLeftMenuItems.addAll(mRightMenuItems); @@ -690,14 +682,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl R.drawable.ic_settings); } - static MenuItem createAppOpsItem(Context context) { - AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate( - R.layout.app_ops_info, null, false); - MenuItem info = new NotificationMenuItem(context, null, appOpsContent, - -1 /*don't show in slow swipe menu */); - return info; - } - static MenuItem createFeedbackItem(Context context) { FeedbackInfo feedbackContent = (FeedbackInfo) LayoutInflater.from(context).inflate( R.layout.feedback_info, null, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 46517711a0dc..3f5867477f16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.row.wrapper; import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; -import android.app.AppOpsManager; import android.app.Notification; import android.content.Context; import android.util.ArraySet; @@ -64,10 +63,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private TextView mHeaderText; private TextView mAppNameText; private ImageView mWorkProfileImage; - private View mCameraIcon; - private View mMicIcon; - private View mOverlayIcon; - private View mAppOps; private View mAudiblyAlertedIcon; private FrameLayout mIconContainer; private View mFeedbackIcon; @@ -109,7 +104,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } }, TRANSFORMING_VIEW_TITLE); resolveHeaderViews(); - addAppOpsOnClickListener(row); addFeedbackOnClickListener(row); } @@ -121,10 +115,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button); mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge); mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header); - mCameraIcon = mView.findViewById(com.android.internal.R.id.camera); - mMicIcon = mView.findViewById(com.android.internal.R.id.mic); - mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay); - mAppOps = mView.findViewById(com.android.internal.R.id.app_ops); mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon); mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback); if (mNotificationHeader != null) { @@ -133,38 +123,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } } - private void addAppOpsOnClickListener(ExpandableNotificationRow row) { - View.OnClickListener listener = row.getAppOpsOnClickListener(); - if (mNotificationHeader != null) { - mNotificationHeader.setAppOpsOnClickListener(listener); - } - if (mAppOps != null) { - mAppOps.setOnClickListener(listener); - } - } - - /** - * Shows or hides 'app op in use' icons based on app usage. - */ - @Override - public void showAppOpsIcons(ArraySet<Integer> appOps) { - if (appOps == null) { - return; - } - if (mOverlayIcon != null) { - mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) - ? View.VISIBLE : View.GONE); - } - if (mCameraIcon != null) { - mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA) - ? View.VISIBLE : View.GONE); - } - if (mMicIcon != null) { - mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO) - ? View.VISIBLE : View.GONE); - } - } - private void addFeedbackOnClickListener(ExpandableNotificationRow row) { View.OnClickListener listener = row.getFeedbackOnClickListener(); if (mNotificationHeader != null) { @@ -304,15 +262,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mHeaderText); } - if (mCameraIcon != null) { - mTransformationHelper.addViewTransformingToSimilar(mCameraIcon); - } - if (mMicIcon != null) { - mTransformationHelper.addViewTransformingToSimilar(mMicIcon); - } - if (mOverlayIcon != null) { - mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon); - } if (mAudiblyAlertedIcon != null) { mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 93d3f3bdbe96..33c939054be5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -22,14 +22,12 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.metrics.LogMaker; import android.os.Handler; -import android.service.notification.StatusBarNotification; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -43,13 +41,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.MediaNotificationView; import com.android.systemui.Dependency; -import com.android.systemui.qs.QSPanel; -import com.android.systemui.qs.QuickQSPanel; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; -import com.android.systemui.util.Utils; import java.util.Timer; import java.util.TimerTask; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 605fbc0f6125..42f5e389d5a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -97,14 +97,6 @@ public abstract class NotificationViewWrapper implements TransformableView { } /** - * Show a set of app opp icons in the layout. - * - * @param appOps which app ops to show - */ - public void showAppOpsIcons(ArraySet<Integer> appOps) { - } - - /** * Shows or hides feedback icon. */ public void showFeedbackIcon(boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 93c2377ccfae..7e4266c7cd28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -1303,20 +1303,6 @@ public class NotificationChildrenContainer extends ViewGroup { } /** - * Show a set of app opp icons in the layout. - * - * @param appOps which app ops to show - */ - public void showAppOpsIcons(ArraySet<Integer> appOps) { - if (mNotificationHeaderWrapper != null) { - mNotificationHeaderWrapper.showAppOpsIcons(appOps); - } - if (mNotificationHeaderWrapperLowPriority != null) { - mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps); - } - } - - /** * Shows or hides feedback icon. */ public void showFeedbackIcon(boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 0e76c904f8cd..9d920c31584c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -47,6 +47,7 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.FileDescriptor; @@ -157,11 +158,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private DozeScrimController mDozeScrimController; private KeyguardViewMediator mKeyguardViewMediator; private ScrimController mScrimController; - private StatusBar mStatusBar; private PendingAuthenticated mPendingAuthenticated = null; private boolean mPendingShowBouncer; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; + private BiometricModeListener mBiometricModeListener; private final MetricsLogger mMetricsLogger; @@ -243,7 +244,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp @Inject public BiometricUnlockController(Context context, DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator, ScrimController scrimController, - StatusBar statusBar, ShadeController shadeController, + ShadeController shadeController, NotificationShadeWindowController notificationShadeWindowController, KeyguardStateController keyguardStateController, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -264,7 +265,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mDozeScrimController = dozeScrimController; mKeyguardViewMediator = keyguardViewMediator; mScrimController = scrimController; - mStatusBar = statusBar; mKeyguardStateController = keyguardStateController; mHandler = handler; mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze); @@ -278,6 +278,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardViewController = keyguardViewController; } + /** Sets a {@link BiometricModeListener}. */ + public void setBiometricModeListener(BiometricModeListener biometricModeListener) { + mBiometricModeListener = biometricModeListener; + } + private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() { @Override public void run() { @@ -434,19 +439,25 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } else { mKeyguardViewMediator.onWakeAndUnlocking(); } - if (mStatusBar.getNavigationBarView() != null) { - mStatusBar.getNavigationBarView().setWakeAndUnlocking(true); - } Trace.endSection(); break; case MODE_ONLY_WAKE: case MODE_NONE: break; } - mStatusBar.notifyBiometricAuthModeChanged(); + onModeChanged(mMode); + if (mBiometricModeListener != null) { + mBiometricModeListener.notifyBiometricAuthModeChanged(); + } Trace.endSection(); } + private void onModeChanged(@WakeAndUnlockMode int mode) { + if (mBiometricModeListener != null) { + mBiometricModeListener.onModeChanged(mode); + } + } + private void showBouncer() { if (mMode == MODE_SHOW_BOUNCER) { mKeyguardViewController.showBouncer(false); @@ -619,10 +630,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMode = MODE_NONE; mBiometricType = null; mNotificationShadeWindowController.setForceDozeBrightness(false); - if (mStatusBar.getNavigationBarView() != null) { - mStatusBar.getNavigationBarView().setWakeAndUnlocking(false); + if (mBiometricModeListener != null) { + mBiometricModeListener.onResetMode(); + mBiometricModeListener.notifyBiometricAuthModeChanged(); } - mStatusBar.notifyBiometricAuthModeChanged(); } @VisibleForTesting @@ -702,4 +713,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp return 3; } } + + /** An interface to interact with the {@link BiometricUnlockController}. */ + public interface BiometricModeListener { + /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */ + void onResetMode(); + /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */ + void onModeChanged(@WakeAndUnlockMode int mode); + /** Called after processing {@link #onModeChanged(int)}. */ + void notifyBiometricAuthModeChanged(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 4afeba8de211..de74d4e04921 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -36,6 +36,7 @@ import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index bc73be19ab59..07b0a4b66c3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -47,7 +47,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.statusbar.RemoteInputController.Callback; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -72,8 +72,8 @@ import javax.inject.Singleton; * Encapsulates all logic for the notification shade window state management. */ @Singleton -public class NotificationShadeWindowController implements Callback, Dumpable, - ConfigurationListener { +public class NotificationShadeWindowControllerImpl implements NotificationShadeWindowController, + Dumpable, ConfigurationListener { private static final String TAG = "NotificationShadeWindowController"; private static final boolean DEBUG = false; @@ -103,7 +103,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, private final SysuiColorExtractor mColorExtractor; @Inject - public NotificationShadeWindowController(Context context, WindowManager windowManager, + public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager, IActivityManager activityManager, DozeParameters dozeParameters, StatusBarStateController statusBarStateController, ConfigurationController configurationController, @@ -147,6 +147,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, /** * Register to receive notifications about status bar window state changes. */ + @Override public void registerCallback(StatusBarWindowCallback callback) { // Prevent adding duplicate callbacks for (int i = 0; i < mCallbacks.size(); i++) { @@ -161,6 +162,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, * Register a listener to monitor scrims visibility * @param listener A listener to monitor scrims visibility */ + @Override public void setScrimsVisibilityListener(Consumer<Integer> listener) { if (listener != null && mScrimsVisibilityListener != listener) { mScrimsVisibilityListener = listener; @@ -176,6 +178,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, /** * Adds the notification shade view to the window manager. */ + @Override public void attach() { // Now that the notification shade encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is @@ -214,14 +217,17 @@ public class NotificationShadeWindowController implements Callback, Dumpable, } } + @Override public void setNotificationShadeView(ViewGroup view) { mNotificationShadeView = view; } + @Override public ViewGroup getNotificationShadeView() { return mNotificationShadeView; } + @Override public void setDozeScreenBrightness(int value) { mScreenBrightnessDoze = value / 255f; } @@ -406,6 +412,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, notifyStateChangedCallbacks(); } + @Override public void notifyStateChangedCallbacks() { for (int i = 0; i < mCallbacks.size(); i++) { StatusBarWindowCallback cb = mCallbacks.get(i).get(); @@ -445,62 +452,74 @@ public class NotificationShadeWindowController implements Callback, Dumpable, } } + @Override public void setKeyguardShowing(boolean showing) { mCurrentState.mKeyguardShowing = showing; apply(mCurrentState); } + @Override public void setKeyguardOccluded(boolean occluded) { mCurrentState.mKeyguardOccluded = occluded; apply(mCurrentState); } + @Override public void setKeyguardNeedsInput(boolean needsInput) { mCurrentState.mKeyguardNeedsInput = needsInput; apply(mCurrentState); } + @Override public void setPanelVisible(boolean visible) { mCurrentState.mPanelVisible = visible; mCurrentState.mNotificationShadeFocusable = visible; apply(mCurrentState); } + @Override public void setNotificationShadeFocusable(boolean focusable) { mCurrentState.mNotificationShadeFocusable = focusable; apply(mCurrentState); } + @Override public void setBouncerShowing(boolean showing) { mCurrentState.mBouncerShowing = showing; apply(mCurrentState); } + @Override public void setBackdropShowing(boolean showing) { mCurrentState.mBackdropShowing = showing; apply(mCurrentState); } + @Override public void setKeyguardFadingAway(boolean keyguardFadingAway) { mCurrentState.mKeyguardFadingAway = keyguardFadingAway; apply(mCurrentState); } + @Override public void setQsExpanded(boolean expanded) { mCurrentState.mQsExpanded = expanded; apply(mCurrentState); } + @Override public void setForceUserActivity(boolean forceUserActivity) { mCurrentState.mForceUserActivity = forceUserActivity; apply(mCurrentState); } - void setLaunchingActivity(boolean launching) { + @Override + public void setLaunchingActivity(boolean launching) { mCurrentState.mLaunchingActivity = launching; apply(mCurrentState); } + @Override public void setScrimsVisibility(int scrimsVisibility) { mCurrentState.mScrimsVisibility = scrimsVisibility; apply(mCurrentState); @@ -512,6 +531,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, * {@link com.android.systemui.statusbar.NotificationShadeDepthController}. * @param backgroundBlurRadius Radius in pixels. */ + @Override public void setBackgroundBlurRadius(int backgroundBlurRadius) { if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) { return; @@ -520,11 +540,13 @@ public class NotificationShadeWindowController implements Callback, Dumpable, apply(mCurrentState); } + @Override public void setHeadsUpShowing(boolean showing) { mCurrentState.mHeadsUpShowing = showing; apply(mCurrentState); } + @Override public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode; apply(mCurrentState); @@ -543,11 +565,13 @@ public class NotificationShadeWindowController implements Callback, Dumpable, * Used for when a heads-up comes in but we still need to wait for the touchable regions to * be computed. */ + @Override public void setForceWindowCollapsed(boolean force) { mCurrentState.mForceCollapsed = force; apply(mCurrentState); } + @Override public void setPanelExpanded(boolean isExpanded) { mCurrentState.mPanelExpanded = isExpanded; apply(mCurrentState); @@ -563,16 +587,19 @@ public class NotificationShadeWindowController implements Callback, Dumpable, * Set whether the screen brightness is forced to the value we use for doze mode by the status * bar window. */ + @Override public void setForceDozeBrightness(boolean forceDozeBrightness) { mCurrentState.mForceDozeBrightness = forceDozeBrightness; apply(mCurrentState); } + @Override public void setDozing(boolean dozing) { mCurrentState.mDozing = dozing; apply(mCurrentState); } + @Override public void setForcePluginOpen(boolean forcePluginOpen) { mCurrentState.mForcePluginOpen = forcePluginOpen; apply(mCurrentState); @@ -584,10 +611,12 @@ public class NotificationShadeWindowController implements Callback, Dumpable, /** * The forcePluginOpen state for the status bar. */ + @Override public boolean getForcePluginOpen() { return mCurrentState.mForcePluginOpen; } + @Override public void setNotTouchable(boolean notTouchable) { mCurrentState.mNotTouchable = notTouchable; apply(mCurrentState); @@ -596,24 +625,29 @@ public class NotificationShadeWindowController implements Callback, Dumpable, /** * Whether the status bar panel is expanded or not. */ + @Override public boolean getPanelExpanded() { return mCurrentState.mPanelExpanded; } + @Override public void setStateListener(OtherwisedCollapsedListener listener) { mListener = listener; } + @Override public void setForcePluginOpenListener(ForcePluginOpenListener listener) { mForcePluginOpenListener = listener; } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(TAG + ":"); pw.println(" mKeyguardDisplayMode=" + mKeyguardDisplayMode); pw.println(mCurrentState); } + @Override public boolean isShowingWallpaper() { return !mCurrentState.mBackdropShowing; } @@ -632,6 +666,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, /** * When keyguard will be dismissed but didn't start animation yet. */ + @Override public void setKeyguardGoingAway(boolean goingAway) { mCurrentState.mKeyguardGoingAway = goingAway; apply(mCurrentState); @@ -642,6 +677,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, * animation is performed, the component should remove itself from the list of features that * are forcing SystemUI to be top-ui. */ + @Override public void setRequestTopUi(boolean requestTopUi, String componentTag) { if (requestTopUi) { mCurrentState.mComponentsForcingTopUi.add(componentTag); @@ -725,23 +761,4 @@ public class NotificationShadeWindowController implements Callback, Dumpable, setDozing(isDozing); } }; - - /** - * Custom listener to pipe data back to plugins about whether or not the status bar would be - * collapsed if not for the plugin. - * TODO: Find cleaner way to do this. - */ - public interface OtherwisedCollapsedListener { - void setWouldOtherwiseCollapse(boolean otherwiseCollapse); - } - - /** - * Listener to indicate forcePluginOpen has changed - */ - public interface ForcePluginOpenListener { - /** - * Called when mState.forcePluginOpen is changed - */ - void onChange(boolean forceOpen); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 42222d724896..53cc2676723c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index 333061547d7e..921bd4faacb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -26,6 +26,7 @@ import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 4fbd49a6fe4f..5b251befb028 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -191,6 +191,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; @@ -1473,6 +1474,34 @@ public class StatusBar extends SystemUI implements DemoMode, protected void startKeyguard() { Trace.beginSection("StatusBar#startKeyguard"); mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); + mBiometricUnlockController.setBiometricModeListener( + new BiometricUnlockController.BiometricModeListener() { + @Override + public void onResetMode() { + setWakeAndUnlocking(false); + } + + @Override + public void onModeChanged(int mode) { + switch (mode) { + case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM: + case BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING: + case BiometricUnlockController.MODE_WAKE_AND_UNLOCK: + setWakeAndUnlocking(true); + } + } + + @Override + public void notifyBiometricAuthModeChanged() { + StatusBar.this.notifyBiometricAuthModeChanged(); + } + + private void setWakeAndUnlocking(boolean wakeAndUnlocking) { + if (getNavigationBarView() != null) { + getNavigationBarView().setWakeAndUnlocking(wakeAndUnlocking); + } + } + }); mStatusBarKeyguardViewManager.registerStatusBar( /* statusBar= */ this, getBouncerContainer(), mNotificationPanelViewController, mBiometricUnlockController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index f3a7c501f6e8..e159fad4ed7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -56,6 +56,7 @@ import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index f1715bef62d2..0ec22b4a415f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 9c7f49090122..b79bb70dddc4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -32,6 +32,7 @@ import android.view.WindowInsets; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.ScreenDecorations; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index dd4af540b2f5..d230eca8d474 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; @@ -80,7 +81,6 @@ import com.android.systemui.statusbar.phone.LightsOutNotifController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index 5257ce4c6bd9..4ae96651b570 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -84,7 +84,7 @@ public class WifiSignalController extends R.bool.config_showWifiIndicatorWhenEnabled); boolean wifiVisible = mCurrentState.enabled && ( (mCurrentState.connected && mCurrentState.inetCondition == 1) - || !mHasMobileDataFeature || mWifiTracker.isDefaultNetwork + || !mHasMobileDataFeature || mCurrentState.isDefault || visibleWhenEnabled); String wifiDesc = mCurrentState.connected ? mCurrentState.ssid : null; boolean ssidPresent = wifiVisible && mCurrentState.ssid != null; @@ -107,6 +107,7 @@ public class WifiSignalController extends public void fetchInitialState() { mWifiTracker.fetchInitialState(); mCurrentState.enabled = mWifiTracker.enabled; + mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; @@ -121,6 +122,7 @@ public class WifiSignalController extends public void handleBroadcast(Intent intent) { mWifiTracker.handleBroadcast(intent); mCurrentState.enabled = mWifiTracker.enabled; + mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; @@ -131,6 +133,7 @@ public class WifiSignalController extends private void handleStatusUpdated() { mCurrentState.statusLabel = mWifiTracker.statusLabel; + mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; notifyListenersIfNecessary(); } @@ -156,6 +159,7 @@ public class WifiSignalController extends static class WifiState extends SignalController.State { String ssid; boolean isTransient; + boolean isDefault; String statusLabel; @Override @@ -164,6 +168,7 @@ public class WifiSignalController extends WifiState state = (WifiState) s; ssid = state.ssid; isTransient = state.isTransient; + isDefault = state.isDefault; statusLabel = state.statusLabel; } @@ -172,6 +177,7 @@ public class WifiSignalController extends super.toString(builder); builder.append(",ssid=").append(ssid) .append(",isTransient=").append(isTransient) + .append(",isDefault=").append(isDefault) .append(",statusLabel=").append(statusLabel); } @@ -183,6 +189,7 @@ public class WifiSignalController extends WifiState other = (WifiState) o; return Objects.equals(other.ssid, ssid) && other.isTransient == isTransient + && other.isDefault == isDefault && TextUtils.equals(other.statusLabel, statusLabel); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java index d4c6f4d6232a..a29db4d98329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java @@ -26,8 +26,6 @@ import android.animation.ObjectAnimator; import android.annotation.IntDef; import android.annotation.UiThread; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.graphics.PixelFormat; import android.provider.DeviceConfig; import android.text.TextUtils; @@ -47,9 +45,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; -import java.util.Queue; import java.util.Set; /** @@ -77,9 +73,6 @@ public class AudioRecordingDisclosureBar implements STATE_NOT_SHOWN, STATE_APPEARING, STATE_SHOWN, - STATE_MINIMIZING, - STATE_MINIMIZED, - STATE_MAXIMIZING, STATE_DISAPPEARING }) public @interface State {} @@ -88,13 +81,9 @@ public class AudioRecordingDisclosureBar implements private static final int STATE_NOT_SHOWN = 0; private static final int STATE_APPEARING = 1; private static final int STATE_SHOWN = 2; - private static final int STATE_MINIMIZING = 3; - private static final int STATE_MINIMIZED = 4; - private static final int STATE_MAXIMIZING = 5; - private static final int STATE_DISAPPEARING = 6; + private static final int STATE_DISAPPEARING = 3; private static final int ANIMATION_DURATION = 600; - private static final int MAXIMIZED_DURATION = 3000; private final Context mContext; private boolean mIsEnabled; @@ -116,30 +105,6 @@ public class AudioRecordingDisclosureBar implements */ private AudioActivityObserver[] mAudioActivityObservers; /** - * Whether the indicator should expand and show the recording application's label. - * If disabled ({@code false}) the "minimized" ({@link #STATE_MINIMIZED}) indicator would appear - * on the screen whenever an application is recording, but will not reveal to the user what - * application this is. - */ - private final boolean mRevealRecordingPackages; - /** - * Set of applications that we've notified the user about since the indicator came up. Meaning - * that if an application is in this list then at some point since the indicator came up, it - * was expanded showing this application's title. - * Used not to notify the user about the same application again while the indicator is shown. - * We empty this set every time the indicator goes off the screen (we always call {@code - * mSessionNotifiedPackages.clear()} before calling {@link #hide()}). - */ - private final Set<String> mSessionNotifiedPackages = new ArraySet<>(); - /** - * If an application starts recording while the TV indicator is neither in {@link - * #STATE_NOT_SHOWN} nor in {@link #STATE_MINIMIZED}, then we add the application's package - * name to the queue, from which we take packages names one by one to disclose the - * corresponding applications' titles to the user, whenever the indicator eventually comes to - * one of the two aforementioned states. - */ - private final Queue<String> mPendingNotificationPackages = new LinkedList<>(); - /** * Set of applications for which we make an exception and do not show the indicator. This gets * populated once - in {@link #AudioRecordingDisclosureBar(Context)}. */ @@ -149,8 +114,6 @@ public class AudioRecordingDisclosureBar implements mContext = context; // Load configs - mRevealRecordingPackages = mContext.getResources().getBoolean( - R.bool.audio_recording_disclosure_reveal_packages); reloadExemptPackages(); mIsEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY, ENABLED_FLAG, true); @@ -210,10 +173,6 @@ public class AudioRecordingDisclosureBar implements if (mState != STATE_NOT_SHOWN) { removeIndicatorView(); } - - // Clean up the state. - mSessionNotifiedPackages.clear(); - mPendingNotificationPackages.clear(); } @UiThread @@ -231,68 +190,29 @@ public class AudioRecordingDisclosureBar implements } if (active) { - showIndicatorForPackageIfNeeded(packageName); + showIfNotShown(); } else { hideIndicatorIfNeeded(); } } @UiThread - private void showIndicatorForPackageIfNeeded(String packageName) { - if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName); - if (!mSessionNotifiedPackages.add(packageName)) { - // We've already notified user about this app, no need to do it again. - if (DEBUG) Log.d(TAG, " - already notified"); - return; - } - - switch (mState) { - case STATE_NOT_SHOWN: - show(packageName); - break; - - case STATE_MINIMIZED: - if (mRevealRecordingPackages) { - expand(packageName); - } - break; - - case STATE_DISAPPEARING: - case STATE_APPEARING: - case STATE_MAXIMIZING: - case STATE_SHOWN: - case STATE_MINIMIZING: - // Currently animating or expanded. Thus add to the pending notifications, and it - // will be picked up once the indicator comes to the STATE_MINIMIZED. - mPendingNotificationPackages.add(packageName); - break; - } - } - - @UiThread private void hideIndicatorIfNeeded() { - // If not MINIMIZED, will check whether the indicator should be hidden when the indicator - // comes to the STATE_MINIMIZED eventually. - if (mState != STATE_MINIMIZED) return; - - // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore. - for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) { - for (String activePackage : mAudioActivityObservers[index].getActivePackages()) { - if (mExemptPackages.contains(activePackage)) continue; - return; - } + // If not STATE_APPEARING, will check whether the indicator should be hidden when the + // indicator comes to the STATE_SHOWN. + // If STATE_DISAPPEARING or STATE_SHOWN - nothing else for us to do here. + if (mState != STATE_SHOWN) return; + + // If is in the STATE_SHOWN and there are no active recorders - hide. + if (!hasActiveRecorders()) { + hide(); } - - // Clear the state and hide the indicator. - mSessionNotifiedPackages.clear(); - hide(); } @UiThread - private void show(String packageName) { - if (DEBUG) { - Log.d(TAG, "Showing indicator"); - } + private void showIfNotShown() { + if (mState != STATE_NOT_SHOWN) return; + if (DEBUG) Log.d(TAG, "Showing indicator"); mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; @@ -308,30 +228,14 @@ public class AudioRecordingDisclosureBar implements mTextView = mTextsContainers.findViewById(R.id.text); mBgEnd = mIndicatorView.findViewById(R.id.bg_end); - // Set up the notification text - if (mRevealRecordingPackages) { - // Swap background drawables depending on layout directions (both drawables have rounded - // corners only on one side) - if (mIsLtr) { - mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded); - mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded); - } else { - mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded); - mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded); - } - - final String label = getApplicationLabel(packageName); - mTextView.setText(mContext.getString(R.string.app_accessed_mic, label)); - } else { - mTextsContainers.setVisibility(View.GONE); - mIconContainerBg.setVisibility(View.GONE); - mTextView.setVisibility(View.GONE); - mBgEnd.setVisibility(View.GONE); - mTextsContainers = null; - mIconContainerBg = null; - mTextView = null; - mBgEnd = null; - } + mTextsContainers.setVisibility(View.GONE); + mIconContainerBg.setVisibility(View.GONE); + mTextView.setVisibility(View.GONE); + mBgEnd.setVisibility(View.GONE); + mTextsContainers = null; + mIconContainerBg = null; + mTextView = null; + mBgEnd = null; // Initially change the visibility to INVISIBLE, wait until and receives the size and // then animate it moving from "off" the screen correctly @@ -366,9 +270,7 @@ public class AudioRecordingDisclosureBar implements @Override public void onAnimationStart(Animator animation, boolean isReverse) { - if (mState == STATE_STOPPED) { - return; - } + if (mState == STATE_STOPPED) return; // Indicator is INVISIBLE at the moment, change it. mIndicatorView.setVisibility(View.VISIBLE); @@ -376,11 +278,7 @@ public class AudioRecordingDisclosureBar implements @Override public void onAnimationEnd(Animator animation) { - if (mRevealRecordingPackages) { - onExpanded(); - } else { - onMinimized(); - } + onAppeared(); } }); set.start(); @@ -404,60 +302,9 @@ public class AudioRecordingDisclosureBar implements } @UiThread - private void expand(String packageName) { - assertRevealingRecordingPackages(); - - final String label = getApplicationLabel(packageName); - mTextView.setText(mContext.getString(R.string.app_accessed_mic, label)); - - final AnimatorSet set = new AnimatorSet(); - set.playTogether( - ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, 0), - ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 1f), - ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 1f), - ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 1f)); - set.setDuration(ANIMATION_DURATION); - set.addListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onExpanded(); - } - }); - set.start(); - - mState = STATE_MAXIMIZING; - } - - @UiThread - private void minimize() { - assertRevealingRecordingPackages(); - - final int targetOffset = (mIsLtr ? 1 : -1) * mTextsContainers.getWidth(); - final AnimatorSet set = new AnimatorSet(); - set.playTogether( - ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, targetOffset), - ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 0f), - ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 0f), - ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 0f)); - set.setDuration(ANIMATION_DURATION); - set.addListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onMinimized(); - } - }); - set.start(); - - mState = STATE_MINIMIZING; - } - - @UiThread private void hide() { - if (DEBUG) { - Log.d(TAG, "Hide indicator"); - } + if (DEBUG) Log.d(TAG, "Hide indicator"); + final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth() - (int) mIconTextsContainer.getTranslationX()); final AnimatorSet set = new AnimatorSet(); @@ -477,49 +324,37 @@ public class AudioRecordingDisclosureBar implements mState = STATE_DISAPPEARING; } - @UiThread - private void onExpanded() { - if (mState == STATE_STOPPED) { - return; - } - - assertRevealingRecordingPackages(); - - mState = STATE_SHOWN; - - mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION); - } @UiThread - private void onMinimized() { - if (mState == STATE_STOPPED) { - return; - } + private void onAppeared() { + if (mState == STATE_STOPPED) return; - mState = STATE_MINIMIZED; + mState = STATE_SHOWN; - if (mRevealRecordingPackages && !mPendingNotificationPackages.isEmpty()) { - // There is a new application that started recording, tell the user about it. - expand(mPendingNotificationPackages.poll()); - } else { - hideIndicatorIfNeeded(); - } + hideIndicatorIfNeeded(); } @UiThread private void onHidden() { - if (mState == STATE_STOPPED) { - return; - } + if (mState == STATE_STOPPED) return; removeIndicatorView(); mState = STATE_NOT_SHOWN; - // Check if anybody started recording while we were in STATE_DISAPPEARING - if (!mPendingNotificationPackages.isEmpty()) { - // There is a new application that started recording, tell the user about it. - show(mPendingNotificationPackages.poll()); + if (hasActiveRecorders()) { + // Got new recorders, show again. + showIfNotShown(); + } + } + + private boolean hasActiveRecorders() { + for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) { + for (String activePackage : mAudioActivityObservers[index].getActivePackages()) { + if (mExemptPackages.contains(activePackage)) continue; + return true; + } } + return false; } private void removeIndicatorView() { @@ -536,26 +371,6 @@ public class AudioRecordingDisclosureBar implements mBgEnd = null; } - private String getApplicationLabel(String packageName) { - assertRevealingRecordingPackages(); - - final PackageManager pm = mContext.getPackageManager(); - final ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - return packageName; - } - return pm.getApplicationLabel(appInfo).toString(); - } - - private void assertRevealingRecordingPackages() { - if (!mRevealRecordingPackages) { - Log.e(TAG, "Not revealing recording packages", - DEBUG ? new RuntimeException("Should not be called") : null); - } - } - private static List<String> splitByComma(String string) { return TextUtils.isEmpty(string) ? Collections.emptyList() : Arrays.asList( string.split(",")); diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 0baecd51653f..228b2ea3cf88 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -45,12 +45,14 @@ import com.android.systemui.stackdivider.DividerModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.DozeServiceHost; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.ShadeControllerImpl; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -162,5 +164,9 @@ public abstract class TvSystemUIModule { StatusBarKeyguardViewManager statusBarKeyguardViewManager); @Binds + abstract NotificationShadeWindowController bindNotificationShadeController( + NotificationShadeWindowControllerImpl notificationShadeWindowController); + + @Binds abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index 60f0cd9da5f2..e967a5d607eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -82,8 +82,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { allowTestableLooperAsMainThread(); MockitoAnnotations.initMocks(this); - mFsc = new ForegroundServiceController( - mEntryManager, mAppOpsController, mMainHandler); + mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler); mListener = new ForegroundServiceNotificationListener( mContext, mFsc, mEntryManager, mNotifPipeline, mock(ForegroundServiceLifetimeExtender.class), mClock); @@ -115,85 +114,6 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { } @Test - public void testAppOps_appOpChangedBeforeNotificationExists() { - // GIVEN app op exists, but notification doesn't exist in NEM yet - NotificationEntry entry = createFgEntry(); - mFsc.onAppOpChanged( - AppOpsManager.OP_CAMERA, - entry.getSbn().getUid(), - entry.getSbn().getPackageName(), - true); - assertFalse(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA)); - - // WHEN the notification is added - mEntryListener.onPendingEntryAdded(entry); - - // THEN the app op is added to the entry - Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA)); - } - - @Test - public void testAppOps_appOpAddedToForegroundNotif() { - // GIVEN a notification associated with a foreground service - NotificationEntry entry = addFgEntry(); - when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry); - - // WHEN we are notified of a new app op for this notification - mFsc.onAppOpChanged( - AppOpsManager.OP_CAMERA, - entry.getSbn().getUid(), - entry.getSbn().getPackageName(), - true); - - // THEN the app op is added to the entry - Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA)); - - // THEN notification views are updated since the notification is visible - verify(mEntryManager, times(1)).updateNotifications(anyString()); - } - - @Test - public void testAppOpsAlreadyAdded() { - // GIVEN a foreground service associated notification that already has the correct app op - NotificationEntry entry = addFgEntry(); - entry.mActiveAppOps.add(AppOpsManager.OP_CAMERA); - when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry); - - // WHEN we are notified of the same app op for this notification - mFsc.onAppOpChanged( - AppOpsManager.OP_CAMERA, - entry.getSbn().getUid(), - entry.getSbn().getPackageName(), - true); - - // THEN the app op still exists in the notification entry - Assert.assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA)); - - // THEN notification views aren't updated since nothing changed - verify(mEntryManager, never()).updateNotifications(anyString()); - } - - @Test - public void testAppOps_appOpNotAddedToUnrelatedNotif() { - // GIVEN no notification entries correspond to the newly updated appOp - NotificationEntry entry = addFgEntry(); - when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(null); - - // WHEN a new app op is detected - mFsc.onAppOpChanged( - AppOpsManager.OP_CAMERA, - entry.getSbn().getUid(), - entry.getSbn().getPackageName(), - true); - - // THEN we won't see appOps on the entry - Assert.assertFalse(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA)); - - // THEN notification views aren't updated since nothing changed - verify(mEntryManager, never()).updateNotifications(anyString()); - } - - @Test public void testAppOpsCRUD() { // no crash on remove that doesn't exist mFsc.onAppOpChanged(9, 1000, "pkg1", false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 5c86fcb6b008..a7808ad54d63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -60,7 +60,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; @@ -71,9 +70,7 @@ import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; -import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -87,7 +84,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; @@ -95,7 +92,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.InjectionInflationController; import com.google.common.collect.ImmutableList; @@ -159,7 +155,7 @@ public class BubbleControllerTest extends SysuiTestCase { private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor; private TestableBubbleController mBubbleController; - private NotificationShadeWindowController mNotificationShadeWindowController; + private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; private NotificationEntryListener mEntryListener; private NotificationRemoveInterceptor mRemoveInterceptor; @@ -199,8 +195,6 @@ public class BubbleControllerTest extends SysuiTestCase { private TestableLooper mTestableLooper; - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; - @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -210,25 +204,8 @@ public class BubbleControllerTest extends SysuiTestCase { mContext.addMockSystemService(FaceManager.class, mFaceManager); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, - new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent() - .createViewInstanceCreatorFactory()), - new NotificationShelfComponent.Builder() { - @Override - public NotificationShelfComponent.Builder notificationShelf( - NotificationShelf view) { - return this; - } - - @Override - public NotificationShelfComponent build() { - return mNotificationShelfComponent; - } - }, - mLockIconController); - // Bubbles get added to status bar window view - mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, + mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, mColorExtractor, mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 196aa65bd28f..3df0df688073 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -83,7 +83,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; @@ -153,7 +153,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; private TestableBubbleController mBubbleController; - private NotificationShadeWindowController mNotificationShadeWindowController; + private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; private NotifCollectionListener mEntryListener; private NotificationTestHelper mNotificationTestHelper; private ExpandableNotificationRow mRow; @@ -218,7 +218,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mLockIconController); // Bubbles get added to status bar window view - mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, + mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, mColorExtractor, mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index 0a6d0716cb85..51ca2a4e5966 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -27,11 +27,11 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 4c5495474018..c8e0f490a783 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -71,7 +71,7 @@ import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.settings.CurrentUserContextTracker; import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.RingerModeLiveData; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 2e794a40d238..89538ac8bc9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -34,19 +33,23 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; -import java.util.Map; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class MediaDataCombineLatestTest extends SysuiTestCase { + @Rule public MockitoRule mockito = MockitoJUnit.rule(); + private static final String KEY = "TEST_KEY"; private static final String OLD_KEY = "TEST_KEY_OLD"; private static final String APP = "APP"; @@ -59,27 +62,14 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { private MediaDataCombineLatest mManager; - @Mock private MediaDataManager mDataSource; - @Mock private MediaDeviceManager mDeviceSource; @Mock private MediaDataManager.Listener mListener; - private MediaDataManager.Listener mDataListener; - private MediaDeviceManager.Listener mDeviceListener; - private MediaData mMediaData; private MediaDeviceData mDeviceData; @Before public void setUp() { - mDataSource = mock(MediaDataManager.class); - mDeviceSource = mock(MediaDeviceManager.class); - mListener = mock(MediaDataManager.Listener.class); - - mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource); - - mDataListener = captureDataListener(); - mDeviceListener = captureDeviceListener(); - + mManager = new MediaDataCombineLatest(); mManager.addListener(mListener); mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, @@ -91,7 +81,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void eventNotEmittedWithoutDevice() { // WHEN data source emits an event without device data - mDataListener.onMediaDataLoaded(KEY, null, mMediaData); + mManager.onMediaDataLoaded(KEY, null, mMediaData); // THEN an event isn't emitted verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @@ -99,7 +89,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void eventNotEmittedWithoutMedia() { // WHEN device source emits an event without media data - mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); + mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // THEN an event isn't emitted verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @@ -107,9 +97,9 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void emitEventAfterDeviceFirst() { // GIVEN that a device event has already been received - mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); + mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // WHEN media event is received - mDataListener.onMediaDataLoaded(KEY, null, mMediaData); + mManager.onMediaDataLoaded(KEY, null, mMediaData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); @@ -119,9 +109,9 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void emitEventAfterMediaFirst() { // GIVEN that media event has already been received - mDataListener.onMediaDataLoaded(KEY, null, mMediaData); + mManager.onMediaDataLoaded(KEY, null, mMediaData); // WHEN device event is received - mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); + mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); @@ -131,11 +121,11 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void migrateKeyMediaFirst() { // GIVEN that media and device info has already been received - mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); - mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); reset(mListener); // WHEN a key migration event is received - mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); + mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture()); @@ -145,11 +135,11 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void migrateKeyDeviceFirst() { // GIVEN that media and device info has already been received - mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); - mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); reset(mListener); // WHEN a key migration event is received - mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); + mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture()); @@ -159,12 +149,12 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void migrateKeyMediaAfter() { // GIVEN that media and device info has already been received - mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); - mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); - mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); + mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); reset(mListener); // WHEN a second key migration event is received for media - mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); + mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); // THEN the key has already been migrated ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture()); @@ -174,12 +164,12 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void migrateKeyDeviceAfter() { // GIVEN that media and device info has already been received - mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData); - mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); - mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); + mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData); + mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData); + mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData); reset(mListener); // WHEN a second key migration event is received for the device - mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); + mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData); // THEN the key has already be migrated ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture()); @@ -189,60 +179,34 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemoved() { // WHEN media data is removed without first receiving device or data - mDataListener.onMediaDataRemoved(KEY); + mManager.onMediaDataRemoved(KEY); // THEN a removed event isn't emitted verify(mListener, never()).onMediaDataRemoved(eq(KEY)); } @Test public void mediaDataRemovedAfterMediaEvent() { - mDataListener.onMediaDataLoaded(KEY, null, mMediaData); - mDataListener.onMediaDataRemoved(KEY); + mManager.onMediaDataLoaded(KEY, null, mMediaData); + mManager.onMediaDataRemoved(KEY); verify(mListener).onMediaDataRemoved(eq(KEY)); } @Test public void mediaDataRemovedAfterDeviceEvent() { - mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); - mDataListener.onMediaDataRemoved(KEY); + mManager.onMediaDeviceChanged(KEY, null, mDeviceData); + mManager.onMediaDataRemoved(KEY); verify(mListener).onMediaDataRemoved(eq(KEY)); } @Test public void mediaDataKeyUpdated() { // GIVEN that device and media events have already been received - mDataListener.onMediaDataLoaded(KEY, null, mMediaData); - mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); + mManager.onMediaDataLoaded(KEY, null, mMediaData); + mManager.onMediaDeviceChanged(KEY, null, mDeviceData); // WHEN the key is changed - mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData); + mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData); // THEN the listener gets a load event with the correct keys ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture()); } - - @Test - public void getDataIncludesDevice() { - // GIVEN that device and media events have been received - mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData); - mDataListener.onMediaDataLoaded(KEY, null, mMediaData); - - // THEN the result of getData includes device info - Map<String, MediaData> results = mManager.getData(); - assertThat(results.get(KEY)).isNotNull(); - assertThat(results.get(KEY).getDevice()).isEqualTo(mDeviceData); - } - - private MediaDataManager.Listener captureDataListener() { - ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass( - MediaDataManager.Listener.class); - verify(mDataSource).addListener(captor.capture()); - return captor.getValue(); - } - - private MediaDeviceManager.Listener captureDeviceListener() { - ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass( - MediaDeviceManager.Listener.class); - verify(mDeviceSource).addListener(captor.capture()); - return captor.getValue(); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index afb64a7649b4..36b6527167f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -32,6 +32,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.concurrent.Executor @@ -56,8 +57,6 @@ private fun <T> any(): T = Mockito.any() class MediaDataFilterTest : SysuiTestCase() { @Mock - private lateinit var combineLatest: MediaDataCombineLatest - @Mock private lateinit var listener: MediaDataManager.Listener @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher @@ -78,8 +77,9 @@ class MediaDataFilterTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener, - mediaDataManager, lockscreenUserManager, executor) + mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener, + lockscreenUserManager, executor) + mediaDataFilter.mediaDataManager = mediaDataManager mediaDataFilter.addListener(listener) // Start all tests as main user @@ -152,8 +152,9 @@ class MediaDataFilterTest : SysuiTestCase() { @Test fun testOnUserSwitched_addsNewUserControls() { // GIVEN that we had some media for both users - val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest) - `when`(combineLatest.getData()).thenReturn(dataMap) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) + mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest) + reset(listener) // and we switch to guest user setUser(USER_GUEST) @@ -213,4 +214,4 @@ class MediaDataFilterTest : SysuiTestCase() { verify(mediaDataManager).setTimedOut(eq(KEY), eq(true)) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 457d559449e1..84c1bf932b5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -16,6 +16,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -24,9 +25,13 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @@ -48,6 +53,7 @@ private fun <T> anyObject(): T { @RunWith(AndroidTestingRunner::class) class MediaDataManagerTest : SysuiTestCase() { + @JvmField @Rule val mockito = MockitoJUnit.rule() @Mock lateinit var mediaControllerFactory: MediaControllerFactory @Mock lateinit var controller: MediaController lateinit var session: MediaSession @@ -58,20 +64,38 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var broadcastDispatcher: BroadcastDispatcher @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener @Mock lateinit var mediaResumeListener: MediaResumeListener + @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter + @Mock lateinit var mediaDeviceManager: MediaDeviceManager + @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest + @Mock lateinit var mediaDataFilter: MediaDataFilter + @Mock lateinit var listener: MediaDataManager.Listener @Mock lateinit var pendingIntent: PendingIntent @Mock lateinit var activityStarter: ActivityStarter - @JvmField @Rule val mockito = MockitoJUnit.rule() lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification + @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> @Before fun setup() { foregroundExecutor = FakeExecutor(FakeSystemClock()) backgroundExecutor = FakeExecutor(FakeSystemClock()) - mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor, - mediaControllerFactory, broadcastDispatcher, dumpManager, - mediaTimeoutListener, mediaResumeListener, activityStarter, - useMediaResumption = true, useQsMediaPlayer = true) + mediaDataManager = MediaDataManager( + context = context, + backgroundExecutor = backgroundExecutor, + foregroundExecutor = foregroundExecutor, + mediaControllerFactory = mediaControllerFactory, + broadcastDispatcher = broadcastDispatcher, + dumpManager = dumpManager, + mediaTimeoutListener = mediaTimeoutListener, + mediaResumeListener = mediaResumeListener, + mediaSessionBasedFilter = mediaSessionBasedFilter, + mediaDeviceManager = mediaDeviceManager, + mediaDataCombineLatest = mediaDataCombineLatest, + mediaDataFilter = mediaDataFilter, + activityStarter = activityStarter, + useMediaResumption = true, + useQsMediaPlayer = true + ) session = MediaSession(context, "MediaDataManagerTestSession") mediaNotification = SbnBuilder().run { setPkg(PACKAGE_NAME) @@ -86,6 +110,12 @@ class MediaDataManagerTest : SysuiTestCase() { putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) } whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller) + + // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal + // listeners in the internal processing pipeline. It receives events, but ince it is a + // mock, it doesn't pass those events along the chain to the external listeners. So, just + // treat mediaSessionBasedFilter as a listener for testing. + listener = mediaSessionBasedFilter } @After @@ -115,8 +145,6 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnMetaDataLoaded_callsListener() { - val listener = mock(MediaDataManager.Listener::class.java) - mediaDataManager.addListener(listener) mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject()) @@ -124,90 +152,81 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnMetaDataLoaded_conservesActiveFlag() { - val listener = TestListener() whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller) whenever(controller.metadata).thenReturn(metadataBuilder.build()) mediaDataManager.addListener(listener) mediaDataManager.onNotificationAdded(KEY, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - assertThat(listener.data!!.active).isTrue() + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) + assertThat(mediaDataCaptor.value!!.active).isTrue() } @Test fun testOnNotificationRemoved_callsListener() { - val listener = mock(MediaDataManager.Listener::class.java) - mediaDataManager.addListener(listener) mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) mediaDataManager.onNotificationRemoved(KEY) - verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationRemoved_withResumption() { // GIVEN that the manager has a notification with a resume action - val listener = TestListener() - mediaDataManager.addListener(listener) whenever(controller.metadata).thenReturn(metadataBuilder.build()) mediaDataManager.onNotificationAdded(KEY, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - val data = listener.data!! + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) + val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) // WHEN the notification is removed mediaDataManager.onNotificationRemoved(KEY) // THEN the media data indicates that it is for resumption - assertThat(listener.data!!.resumption).isTrue() - // AND the new key is the package name - assertThat(listener.key!!).isEqualTo(PACKAGE_NAME) - assertThat(listener.oldKey!!).isEqualTo(KEY) - assertThat(listener.removedKey).isNull() + verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor)) + assertThat(mediaDataCaptor.value.resumption).isTrue() } @Test fun testOnNotificationRemoved_twoWithResumption() { // GIVEN that the manager has two notifications with resume actions - val listener = TestListener() - mediaDataManager.addListener(listener) whenever(controller.metadata).thenReturn(metadataBuilder.build()) mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onNotificationAdded(KEY_2, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(2) assertThat(foregroundExecutor.runAllReady()).isEqualTo(2) - val data = listener.data!! + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) + val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() val resumableData = data.copy(resumeAction = Runnable {}) mediaDataManager.onMediaDataLoaded(KEY, null, resumableData) mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData) + reset(listener) // WHEN the first is removed mediaDataManager.onNotificationRemoved(KEY) // THEN the data is for resumption and the key is migrated to the package name - assertThat(listener.data!!.resumption).isTrue() - assertThat(listener.key!!).isEqualTo(PACKAGE_NAME) - assertThat(listener.oldKey!!).isEqualTo(KEY) - assertThat(listener.removedKey).isNull() + verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor)) + assertThat(mediaDataCaptor.value.resumption).isTrue() + verify(listener, never()).onMediaDataRemoved(eq(KEY)) // WHEN the second is removed mediaDataManager.onNotificationRemoved(KEY_2) // THEN the data is for resumption and the second key is removed - assertThat(listener.data!!.resumption).isTrue() - assertThat(listener.key!!).isEqualTo(PACKAGE_NAME) - assertThat(listener.oldKey!!).isEqualTo(PACKAGE_NAME) - assertThat(listener.removedKey!!).isEqualTo(KEY_2) + verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(PACKAGE_NAME), + capture(mediaDataCaptor)) + assertThat(mediaDataCaptor.value.resumption).isTrue() + verify(listener).onMediaDataRemoved(eq(KEY_2)) } @Test fun testAppBlockedFromResumption() { // GIVEN that the manager has a notification with a resume action - val listener = TestListener() - mediaDataManager.addListener(listener) whenever(controller.metadata).thenReturn(metadataBuilder.build()) mediaDataManager.onNotificationAdded(KEY, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - val data = listener.data!! + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) + val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) @@ -219,7 +238,7 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed - assertThat(listener.removedKey!!).isEqualTo(KEY) + verify(listener).onMediaDataRemoved(eq(KEY)) } @Test @@ -229,13 +248,12 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.appsBlockedFromResume = blocked // and GIVEN that the manager has a notification from that app with a resume action - val listener = TestListener() - mediaDataManager.addListener(listener) whenever(controller.metadata).thenReturn(metadataBuilder.build()) mediaDataManager.onNotificationAdded(KEY, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - val data = listener.data!! + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor)) + val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) @@ -246,14 +264,11 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the entry will stay as a resume control - assertThat(listener.key!!).isEqualTo(PACKAGE_NAME) - assertThat(listener.oldKey!!).isEqualTo(KEY) + verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor)) } @Test fun testAddResumptionControls() { - val listener = TestListener() - mediaDataManager.addListener(listener) // WHEN resumption controls are added` val desc = MediaDescription.Builder().run { setTitle(SESSION_TITLE) @@ -264,7 +279,8 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) // THEN the media data indicates that it is for resumption - val data = listener.data!! + verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor)) + val data = mediaDataCaptor.value assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) assertThat(data.app).isEqualTo(APP_NAME) @@ -273,8 +289,6 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testDismissMedia_listenerCalled() { - val listener = mock(MediaDataManager.Listener::class.java) - mediaDataManager.addListener(listener) mediaDataManager.onNotificationAdded(KEY, mediaNotification) mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java)) mediaDataManager.dismissMediaData(KEY, 0L) @@ -284,26 +298,4 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } - - /** - * Simple implementation of [MediaDataManager.Listener] for the test. - * - * Giving up on trying to get a mock Listener and ArgumentCaptor to work. - */ - private class TestListener : MediaDataManager.Listener { - var data: MediaData? = null - var key: String? = null - var oldKey: String? = null - var removedKey: String? = null - - override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - this.key = key - this.oldKey = oldKey - this.data = data - } - - override fun onMediaDataRemoved(key: String) { - removedKey = key - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 7bc15dd46cd6..fdb432cc097c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -68,7 +68,6 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var manager: MediaDeviceManager - @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var lmmFactory: LocalMediaManagerFactory @Mock private lateinit var lmm: LocalMediaManager @Mock private lateinit var mr2: MediaRouter2Manager @@ -91,7 +90,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fakeFgExecutor = FakeExecutor(FakeSystemClock()) fakeBgExecutor = FakeExecutor(FakeSystemClock()) manager = MediaDeviceManager(context, lmmFactory, mr2, fakeFgExecutor, fakeBgExecutor, - mediaDataManager, dumpster) + dumpster) manager.addListener(listener) // Configure mocks. diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt new file mode 100644 index 000000000000..887cc777d4fe --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt @@ -0,0 +1,383 @@ +/* + * 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.graphics.Color +import android.media.session.MediaController +import android.media.session.MediaController.PlaybackInfo +import android.media.session.MediaSession +import android.media.session.MediaSessionManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest + +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock + +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever + +private const val PACKAGE = "PKG" +private const val KEY = "TEST_KEY" +private const val NOTIF_KEY = "TEST_KEY" +private const val SESSION_ARTIST = "SESSION_ARTIST" +private const val SESSION_TITLE = "SESSION_TITLE" +private const val APP_NAME = "APP_NAME" +private const val USER_ID = 0 + +private val info = MediaData( + userId = USER_ID, + initialized = true, + backgroundColor = Color.DKGRAY, + app = APP_NAME, + appIcon = null, + artist = SESSION_ARTIST, + song = SESSION_TITLE, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = PACKAGE, + token = null, + clickIntent = null, + device = null, + active = true, + resumeAction = null, + resumption = false, + notificationKey = NOTIF_KEY, + hasCheckedForResume = false +) + +private fun <T> eq(value: T): T = Mockito.eq(value) ?: value + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +public class MediaSessionBasedFilterTest : SysuiTestCase() { + + @JvmField @Rule val mockito = MockitoJUnit.rule() + + // Unit to be tested + private lateinit var filter: MediaSessionBasedFilter + + private lateinit var sessionListener: MediaSessionManager.OnActiveSessionsChangedListener + @Mock private lateinit var mediaListener: MediaDataManager.Listener + + // MediaSessionBasedFilter dependencies + @Mock private lateinit var mediaSessionManager: MediaSessionManager + private lateinit var fgExecutor: FakeExecutor + private lateinit var bgExecutor: FakeExecutor + + @Mock private lateinit var controller1: MediaController + @Mock private lateinit var controller2: MediaController + @Mock private lateinit var controller3: MediaController + @Mock private lateinit var controller4: MediaController + + private lateinit var token1: MediaSession.Token + private lateinit var token2: MediaSession.Token + private lateinit var token3: MediaSession.Token + private lateinit var token4: MediaSession.Token + + @Mock private lateinit var remotePlaybackInfo: PlaybackInfo + @Mock private lateinit var localPlaybackInfo: PlaybackInfo + + private lateinit var session1: MediaSession + private lateinit var session2: MediaSession + private lateinit var session3: MediaSession + private lateinit var session4: MediaSession + + private lateinit var mediaData1: MediaData + private lateinit var mediaData2: MediaData + private lateinit var mediaData3: MediaData + private lateinit var mediaData4: MediaData + + @Before + fun setUp() { + fgExecutor = FakeExecutor(FakeSystemClock()) + bgExecutor = FakeExecutor(FakeSystemClock()) + filter = MediaSessionBasedFilter(context, mediaSessionManager, fgExecutor, bgExecutor) + + // Configure mocks. + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(emptyList()) + + session1 = MediaSession(context, "MediaSessionBasedFilter1") + session2 = MediaSession(context, "MediaSessionBasedFilter2") + session3 = MediaSession(context, "MediaSessionBasedFilter3") + session4 = MediaSession(context, "MediaSessionBasedFilter4") + + token1 = session1.sessionToken + token2 = session2.sessionToken + token3 = session3.sessionToken + token4 = session4.sessionToken + + whenever(controller1.getSessionToken()).thenReturn(token1) + whenever(controller2.getSessionToken()).thenReturn(token2) + whenever(controller3.getSessionToken()).thenReturn(token3) + whenever(controller4.getSessionToken()).thenReturn(token4) + + whenever(controller1.getPackageName()).thenReturn(PACKAGE) + whenever(controller2.getPackageName()).thenReturn(PACKAGE) + whenever(controller3.getPackageName()).thenReturn(PACKAGE) + whenever(controller4.getPackageName()).thenReturn(PACKAGE) + + mediaData1 = info.copy(token = token1) + mediaData2 = info.copy(token = token2) + mediaData3 = info.copy(token = token3) + mediaData4 = info.copy(token = token4) + + whenever(remotePlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) + whenever(localPlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) + + whenever(controller1.getPlaybackInfo()).thenReturn(localPlaybackInfo) + whenever(controller2.getPlaybackInfo()).thenReturn(localPlaybackInfo) + whenever(controller3.getPlaybackInfo()).thenReturn(localPlaybackInfo) + whenever(controller4.getPlaybackInfo()).thenReturn(localPlaybackInfo) + + // Capture listener + bgExecutor.runAllReady() + val listenerCaptor = ArgumentCaptor.forClass( + MediaSessionManager.OnActiveSessionsChangedListener::class.java) + verify(mediaSessionManager).addOnActiveSessionsChangedListener( + listenerCaptor.capture(), any()) + sessionListener = listenerCaptor.value + + filter.addListener(mediaListener) + } + + @After + fun tearDown() { + session1.release() + session2.release() + session3.release() + session4.release() + } + + @Test + fun noMediaSession_loadedEventNotFiltered() { + filter.onMediaDataLoaded(KEY, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1)) + } + + @Test + fun noMediaSession_removedEventNotFiltered() { + filter.onMediaDataRemoved(KEY) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + verify(mediaListener).onMediaDataRemoved(eq(KEY)) + } + + @Test + fun matchingMediaSession_loadedEventNotFiltered() { + // GIVEN an active session + val controllers = listOf(controller1) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received that matches the session + filter.onMediaDataLoaded(KEY, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1)) + } + + @Test + fun matchingMediaSession_removedEventNotFiltered() { + // GIVEN an active session + val controllers = listOf(controller1) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a removed event is received + filter.onMediaDataRemoved(KEY) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataRemoved(eq(KEY)) + } + + @Test + fun remoteSession_loadedEventNotFiltered() { + // GIVEN a remove session + whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + val controllers = listOf(controller1) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received that matche the session + filter.onMediaDataLoaded(KEY, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1)) + } + + @Test + fun remoteAndLocalSessions_localLoadedEventFiltered() { + // GIVEN remote and local sessions + whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + val controllers = listOf(controller1, controller2) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received that matches the remote session + filter.onMediaDataLoaded(KEY, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1)) + // WHEN a loaded event is received that matches the local session + filter.onMediaDataLoaded(KEY, null, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is filtered + verify(mediaListener, never()).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2)) + } + + @Test + fun remoteAndLocalHaveDifferentKeys_localLoadedEventFiltered() { + // GIVEN remote and local sessions + val key1 = "KEY_1" + val key2 = "KEY_2" + whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + val controllers = listOf(controller1, controller2) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received that matches the remote session + filter.onMediaDataLoaded(key1, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1)) + // WHEN a loaded event is received that matches the local session + filter.onMediaDataLoaded(key2, null, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is filtered + verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2)) + // AND there should be a removed event for key2 + verify(mediaListener).onMediaDataRemoved(eq(key2)) + } + + @Test + fun multipleRemoteSessions_loadedEventNotFiltered() { + // GIVEN two remote sessions + whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + val controllers = listOf(controller1, controller2) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received that matches the remote session + filter.onMediaDataLoaded(KEY, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1)) + // WHEN a loaded event is received that matches the local session + filter.onMediaDataLoaded(KEY, null, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2)) + } + + @Test + fun multipleOtherSessions_loadedEventNotFiltered() { + // GIVEN multiple active sessions from other packages + val controllers = listOf(controller1, controller2, controller3, controller4) + whenever(controller1.getPackageName()).thenReturn("PKG_1") + whenever(controller2.getPackageName()).thenReturn("PKG_2") + whenever(controller3.getPackageName()).thenReturn("PKG_3") + whenever(controller4.getPackageName()).thenReturn("PKG_4") + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received + filter.onMediaDataLoaded(KEY, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the event is not filtered + verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1)) + } + + @Test + fun doNotFilterDuringKeyMigration() { + val key1 = "KEY_1" + val key2 = "KEY_2" + // GIVEN a loaded event + filter.onMediaDataLoaded(key1, null, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + reset(mediaListener) + // GIVEN remote and local sessions + whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + val controllers = listOf(controller1, controller2) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // WHEN a loaded event is received that matches the local session but it is a key migration + filter.onMediaDataLoaded(key2, key1, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the key migration event is fired + verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2)) + } + + @Test + fun filterAfterKeyMigration() { + val key1 = "KEY_1" + val key2 = "KEY_2" + // GIVEN a loaded event + filter.onMediaDataLoaded(key1, null, mediaData1) + filter.onMediaDataLoaded(key1, null, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + reset(mediaListener) + // GIVEN remote and local sessions + whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo) + val controllers = listOf(controller1, controller2) + whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) + sessionListener.onActiveSessionsChanged(controllers) + // GIVEN that the keys have been migrated + filter.onMediaDataLoaded(key2, key1, mediaData1) + filter.onMediaDataLoaded(key2, key1, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + reset(mediaListener) + // WHEN a loaded event is received that matches the local session + filter.onMediaDataLoaded(key2, null, mediaData2) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the key migration event is filtered + verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2)) + // WHEN a loaded event is received that matches the remote session + filter.onMediaDataLoaded(key2, null, mediaData1) + bgExecutor.runAllReady() + fgExecutor.runAllReady() + // THEN the key migration event is fired + verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java index 644ed3d6e2b5..8089561f44a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java @@ -34,7 +34,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.HeadsUpManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 56df1939be56..a36a4c43e278 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.ActivityLaunchAnimator import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.phone.NotificationShadeWindowController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.eq import org.junit.Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 6f46923cda5e..c35ada7c3501 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -212,19 +212,6 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { } @Test - public void testUpdateNotificationViews_appOps() throws Exception { - NotificationEntry entry0 = createEntry(); - entry0.setRow(spy(entry0.getRow())); - when(mEntryManager.getVisibleNotifications()).thenReturn( - Lists.newArrayList(entry0)); - mListContainer.addContainerView(entry0.getRow()); - - mViewHierarchyManager.updateNotificationViews(); - - verify(entry0.getRow(), times(1)).showAppOpsIcons(any()); - } - - @Test public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() { // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews() mMadeReentrantCall = false; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java index 960ea79f36b4..ce8ce2e39bcc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java @@ -77,8 +77,6 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { private NotificationEntryBuilder mEntryBuilder; private AppOpsCoordinator mAppOpsCoordinator; private NotifFilter mForegroundFilter; - private NotifCollectionListener mNotifCollectionListener; - private AppOpsController.Callback mAppOpsCallback; private NotifLifetimeExtender mForegroundNotifLifetimeExtender; private NotifSection mFgsSection; @@ -113,19 +111,6 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { lifetimeExtenderCaptor.capture()); mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue(); - // capture notifCollectionListener - ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor = - ArgumentCaptor.forClass(NotifCollectionListener.class); - verify(mNotifPipeline, times(1)).addCollectionListener( - notifCollectionCaptor.capture()); - mNotifCollectionListener = notifCollectionCaptor.getValue(); - - // capture app ops callback - ArgumentCaptor<AppOpsController.Callback> appOpsCaptor = - ArgumentCaptor.forClass(AppOpsController.Callback.class); - verify(mAppOpsController).addCallback(any(int[].class), appOpsCaptor.capture()); - mAppOpsCallback = appOpsCaptor.getValue(); - mFgsSection = mAppOpsCoordinator.getSection(); } @@ -230,136 +215,6 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { } @Test - public void testAppOpsUpdateOnlyAppliedToRelevantNotificationWithStandardLayout() { - // GIVEN three current notifications, two with the same key but from different users - NotificationEntry entry1 = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(1) - .build(); - NotificationEntry entry2 = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(2) - .build(); - NotificationEntry entry3_diffUser = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID + 1)) - .setPkg(TEST_PKG) - .setId(2) - .build(); - when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3_diffUser)); - - // GIVEN that only entry2 has a standard layout - when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG)) - .thenReturn(new ArraySet<>(List.of(entry2.getKey()))); - - // WHEN a new app ops code comes in - mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true); - mExecutor.runAllReady(); - - // THEN entry2's app ops are updated, but no one else's are - assertEquals( - new ArraySet<>(), - entry1.mActiveAppOps); - assertEquals( - new ArraySet<>(List.of(47)), - entry2.mActiveAppOps); - assertEquals( - new ArraySet<>(), - entry3_diffUser.mActiveAppOps); - } - - @Test - public void testAppOpsUpdateAppliedToAllNotificationsWithStandardLayouts() { - // GIVEN three notifications with standard layouts - NotificationEntry entry1 = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(1) - .build(); - NotificationEntry entry2 = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(2) - .build(); - NotificationEntry entry3 = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(3) - .build(); - when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry3)); - when(mForegroundServiceController.getStandardLayoutKeys(NOTIF_USER_ID, TEST_PKG)) - .thenReturn(new ArraySet<>(List.of(entry1.getKey(), entry2.getKey(), - entry3.getKey()))); - - // WHEN a new app ops code comes in - mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true); - mExecutor.runAllReady(); - - // THEN all entries get updated - assertEquals( - new ArraySet<>(List.of(47)), - entry1.mActiveAppOps); - assertEquals( - new ArraySet<>(List.of(47)), - entry2.mActiveAppOps); - assertEquals( - new ArraySet<>(List.of(47)), - entry3.mActiveAppOps); - } - - @Test - public void testAppOpsAreRemoved() { - // GIVEN One notification which is associated with app ops - NotificationEntry entry = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(2) - .build(); - when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry)); - when(mForegroundServiceController.getStandardLayoutKeys(0, TEST_PKG)) - .thenReturn(new ArraySet<>(List.of(entry.getKey()))); - - // GIVEN that the notification's app ops are already [47, 33] - mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, true); - mAppOpsCallback.onActiveStateChanged(33, NOTIF_USER_ID, TEST_PKG, true); - mExecutor.runAllReady(); - assertEquals( - new ArraySet<>(List.of(47, 33)), - entry.mActiveAppOps); - - // WHEN one of the app ops is removed - mAppOpsCallback.onActiveStateChanged(47, NOTIF_USER_ID, TEST_PKG, false); - mExecutor.runAllReady(); - - // THEN the entry's active app ops are updated as well - assertEquals( - new ArraySet<>(List.of(33)), - entry.mActiveAppOps); - } - - @Test - public void testNullAppOps() { - // GIVEN one notification with app ops - NotificationEntry entry = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .setPkg(TEST_PKG) - .setId(2) - .build(); - entry.mActiveAppOps.clear(); - entry.mActiveAppOps.addAll(List.of(47, 33)); - - // WHEN the notification is updated and the foreground service controller returns null for - // this notification - when(mForegroundServiceController.getAppOps(entry.getSbn().getUser().getIdentifier(), - entry.getSbn().getPackageName())).thenReturn(null); - mNotifCollectionListener.onEntryUpdated(entry); - - // THEN the entry's active app ops is updated to empty - assertTrue(entry.mActiveAppOps.isEmpty()); - } - - @Test public void testIncludeFGSInSection_importanceDefault() { // GIVEN the notification represents a colorized foreground service with > min importance mEntryBuilder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java deleted file mode 100644 index 43d8b50bcf72..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification.row; - -import static android.app.AppOpsManager.OP_CAMERA; -import static android.app.AppOpsManager.OP_RECORD_AUDIO; -import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Notification; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.UiThreadTest; -import android.util.ArraySet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CountDownLatch; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@UiThreadTest -public class AppOpsInfoTest extends SysuiTestCase { - private static final String TEST_PACKAGE_NAME = "test_package"; - private static final int TEST_UID = 1; - - private AppOpsInfo mAppOpsInfo; - private final PackageManager mMockPackageManager = mock(PackageManager.class); - private final NotificationGuts mGutsParent = mock(NotificationGuts.class); - private StatusBarNotification mSbn; - private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); - - @Before - public void setUp() throws Exception { - // Inflate the layout - final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - mAppOpsInfo = (AppOpsInfo) layoutInflater.inflate(R.layout.app_ops_info, null); - mAppOpsInfo.setGutsParent(mGutsParent); - - // PackageManager must return a packageInfo and applicationInfo. - final PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = TEST_PACKAGE_NAME; - when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt())) - .thenReturn(packageInfo); - final ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.uid = TEST_UID; // non-zero - when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn( - applicationInfo); - - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, - new Notification(), UserHandle.CURRENT, null, 0); - } - - @Test - public void testBindNotification_SetsTextApplicationName() { - when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>()); - final TextView textView = mAppOpsInfo.findViewById(R.id.pkgname); - assertTrue(textView.getText().toString().contains("App Name")); - } - - @Test - public void testBindNotification_SetsPackageIcon() { - final Drawable iconDrawable = mock(Drawable.class); - when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) - .thenReturn(iconDrawable); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>()); - final ImageView iconView = mAppOpsInfo.findViewById(R.id.pkgicon); - assertEquals(iconDrawable, iconView.getDrawable()); - } - - @Test - public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_CAMERA); - final CountDownLatch latch = new CountDownLatch(1); - mAppOpsInfo.bindGuts(mMockPackageManager, (View v, String pkg, int uid, - ArraySet<Integer> ops) -> { - assertEquals(TEST_PACKAGE_NAME, pkg); - assertEquals(expectedOps, ops); - assertEquals(TEST_UID, uid); - latch.countDown(); - }, mSbn, mUiEventLogger, expectedOps); - - final View settingsButton = mAppOpsInfo.findViewById(R.id.settings); - settingsButton.performClick(); - // Verify that listener was triggered. - assertEquals(0, latch.getCount()); - } - - @Test - public void testBindNotification_LogsOpen() throws Exception { - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>()); - assertEquals(1, mUiEventLogger.numLogs()); - assertEquals(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN.getId(), - mUiEventLogger.eventId(0)); - } - - @Test - public void testOk() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_CAMERA); - final CountDownLatch latch = new CountDownLatch(1); - mAppOpsInfo.bindGuts(mMockPackageManager, (View v, String pkg, int uid, - ArraySet<Integer> ops) -> { - assertEquals(TEST_PACKAGE_NAME, pkg); - assertEquals(expectedOps, ops); - assertEquals(TEST_UID, uid); - latch.countDown(); - }, mSbn, mUiEventLogger, expectedOps); - - final View okButton = mAppOpsInfo.findViewById(R.id.ok); - okButton.performClick(); - assertEquals(1, latch.getCount()); - verify(mGutsParent, times(1)).closeControls(eq(okButton), anyBoolean()); - } - - @Test - public void testPrompt_camera() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_CAMERA); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is using the camera.", prompt.getText()); - } - - @Test - public void testPrompt_mic() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_RECORD_AUDIO); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is using the microphone.", prompt.getText()); - } - - @Test - public void testPrompt_overlay() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is displaying over other apps on your screen.", prompt.getText()); - } - - @Test - public void testPrompt_camera_mic() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_CAMERA); - expectedOps.add(OP_RECORD_AUDIO); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is using the microphone and camera.", prompt.getText()); - } - - @Test - public void testPrompt_camera_mic_overlay() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_CAMERA); - expectedOps.add(OP_RECORD_AUDIO); - expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is displaying over other apps on your screen and using" - + " the microphone and camera.", prompt.getText()); - } - - @Test - public void testPrompt_camera_overlay() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_CAMERA); - expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is displaying over other apps on your screen and using" - + " the camera.", prompt.getText()); - } - - @Test - public void testPrompt_mic_overlay() { - ArraySet<Integer> expectedOps = new ArraySet<>(); - expectedOps.add(OP_RECORD_AUDIO); - expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); - TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); - assertEquals("This app is displaying over other apps on your screen and using" - + " the microphone.", prompt.getText()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index dc4a6ca14a77..f29b46c73e4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -35,12 +35,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.AppOpsManager; import android.app.NotificationChannel; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.util.ArraySet; import android.view.View; import androidx.test.filters.SmallTest; @@ -213,46 +211,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testShowAppOps_noHeader() { - // public notification is custom layout - no header - mGroupRow.setSensitive(true, true); - mGroupRow.setAppOpsOnClickListener(null); - mGroupRow.showAppOpsIcons(null); - } - - @Test - public void testShowAppOpsIcons_header() { - NotificationContentView publicLayout = mock(NotificationContentView.class); - mGroupRow.setPublicLayout(publicLayout); - NotificationContentView privateLayout = mock(NotificationContentView.class); - mGroupRow.setPrivateLayout(privateLayout); - NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); - when(mockContainer.getNotificationChildCount()).thenReturn(1); - mGroupRow.setChildrenContainer(mockContainer); - - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS); - mGroupRow.showAppOpsIcons(ops); - - verify(mockContainer, times(1)).showAppOpsIcons(ops); - verify(privateLayout, times(1)).showAppOpsIcons(ops); - verify(publicLayout, times(1)).showAppOpsIcons(ops); - - } - - @Test - public void testAppOpsOnClick() { - ExpandableNotificationRow.CoordinateOnClickListener l = mock( - ExpandableNotificationRow.CoordinateOnClickListener.class); - View view = mock(View.class); - - mGroupRow.setAppOpsOnClickListener(l); - - mGroupRow.getAppOpsOnClickListener().onClick(view); - verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any()); - } - - @Test public void testFeedback_noHeader() { // public notification is custom layout - no header mGroupRow.setSensitive(true, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 6d4a7115b8c4..c2091da2347e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -76,32 +76,6 @@ public class NotificationContentViewTest extends SysuiTestCase { @Test @UiThreadTest - public void testShowAppOpsIcons() { - View mockContracted = mock(NotificationHeaderView.class); - when(mockContracted.findViewById(com.android.internal.R.id.mic)) - .thenReturn(mockContracted); - View mockExpanded = mock(NotificationHeaderView.class); - when(mockExpanded.findViewById(com.android.internal.R.id.mic)) - .thenReturn(mockExpanded); - View mockHeadsUp = mock(NotificationHeaderView.class); - when(mockHeadsUp.findViewById(com.android.internal.R.id.mic)) - .thenReturn(mockHeadsUp); - - mView.setContractedChild(mockContracted); - mView.setExpandedChild(mockExpanded); - mView.setHeadsUpChild(mockHeadsUp); - - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(AppOpsManager.OP_RECORD_AUDIO); - mView.showAppOpsIcons(ops); - - verify(mockContracted, times(1)).setVisibility(View.VISIBLE); - verify(mockExpanded, times(1)).setVisibility(View.VISIBLE); - verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE); - } - - @Test - @UiThreadTest public void testShowFeedbackIcon() { View mockContracted = mock(NotificationHeaderView.class); when(mockContracted.findViewById(com.android.internal.R.id.feedback)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 8ccbb2ebb0db..fa2fa04abaa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -52,6 +52,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -68,7 +69,6 @@ import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.SmartReplyConstants; import org.mockito.ArgumentCaptor; @@ -422,7 +422,6 @@ public class NotificationTestHelper { mock(OnExpandClickListener.class), mock(NotificationMediaManager.class), mock(ExpandableNotificationRow.CoordinateOnClickListener.class), - mock(ExpandableNotificationRow.CoordinateOnClickListener.class), mock(FalsingManager.class), mStatusBarStateController, mPeopleNotificationIdentifier); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 64907eef2dd0..f1c8ece58fda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -43,6 +43,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; @@ -76,7 +77,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { @Mock private ScrimController mScrimController; @Mock - private StatusBar mStatusBar; + private BiometricUnlockController.BiometricModeListener mBiometricModeListener; @Mock private ShadeController mShadeController; @Mock @@ -105,11 +106,12 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager); res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0); mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController, - mKeyguardViewMediator, mScrimController, mStatusBar, mShadeController, + mKeyguardViewMediator, mScrimController, mShadeController, mNotificationShadeWindowController, mKeyguardStateController, mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger, mDumpManager); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); + mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index a6e29181e435..a5f4e51ea13c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -40,6 +40,7 @@ import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index 6d642ec44314..48bf2db0be6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -33,6 +33,7 @@ import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index 8c37cf1514fd..fcea17c5a6b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -57,7 +57,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest -public class NotificationShadeWindowControllerTest extends SysuiTestCase { +public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private WindowManager mWindowManager; @Mock private DozeParameters mDozeParameters; @@ -72,7 +72,7 @@ public class NotificationShadeWindowControllerTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; - private NotificationShadeWindowController mNotificationShadeWindowController; + private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @Before public void setUp() { @@ -80,7 +80,7 @@ public class NotificationShadeWindowControllerTest extends SysuiTestCase { when(mDozeParameters.getAlwaysOn()).thenReturn(true); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, + mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, mColorExtractor, mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 51900cb7dda1..ea57cc9607bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 50d891efc62c..ccc307841491 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -47,6 +47,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index e4f481241257..e46aa04c4504 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index aa0940698510..8a1d95ea4478 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -104,6 +104,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.RemoteInputController; diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 32d02fb17983..96d973ec5272 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -127,6 +127,7 @@ public class VibratorService extends IVibratorService.Stub private final int mPreviousVibrationsLimit; private final boolean mAllowPriorityVibrationsInLowPowerMode; private final List<Integer> mSupportedEffects; + private final List<Integer> mSupportedPrimitives; private final long mCapabilities; private final int mDefaultVibrationAmplitude; private final SparseArray<VibrationEffect> mFallbackEffects; @@ -184,6 +185,8 @@ public class VibratorService extends IVibratorService.Stub static native int[] vibratorGetSupportedEffects(long controllerPtr); + static native int[] vibratorGetSupportedPrimitives(long controllerPtr); + static native long vibratorPerformEffect( long controllerPtr, long effect, long strength, Vibration vibration); @@ -397,6 +400,7 @@ public class VibratorService extends IVibratorService.Stub mNativeWrapper.vibratorOff(); mSupportedEffects = asList(mNativeWrapper.vibratorGetSupportedEffects()); + mSupportedPrimitives = asList(mNativeWrapper.vibratorGetSupportedPrimitives()); mCapabilities = mNativeWrapper.vibratorGetCapabilities(); mContext = context; @@ -647,8 +651,11 @@ public class VibratorService extends IVibratorService.Stub @Override // Binder call public boolean[] arePrimitivesSupported(int[] primitiveIds) { boolean[] supported = new boolean[primitiveIds.length]; - if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { - Arrays.fill(supported, true); + if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) || mSupportedPrimitives == null) { + return supported; + } + for (int i = 0; i < primitiveIds.length; i++) { + supported[i] = mSupportedPrimitives.contains(primitiveIds[i]); } return supported; } @@ -1501,6 +1508,7 @@ public class VibratorService extends IVibratorService.Stub pw.println(" mNotificationIntensity=" + mNotificationIntensity); pw.println(" mRingIntensity=" + mRingIntensity); pw.println(" mSupportedEffects=" + mSupportedEffects); + pw.println(" mSupportedPrimitives=" + mSupportedPrimitives); pw.println(); pw.println(" Previous ring vibrations:"); for (VibrationInfo info : mPreviousRingVibrations) { @@ -1759,6 +1767,11 @@ public class VibratorService extends IVibratorService.Stub return VibratorService.vibratorGetSupportedEffects(mNativeControllerPtr); } + /** Returns all compose primitives supported by the device vibrator. */ + public int[] vibratorGetSupportedPrimitives() { + return VibratorService.vibratorGetSupportedPrimitives(mNativeControllerPtr); + } + /** Turns vibrator on to perform one of the supported effects. */ public long vibratorPerformEffect(long effect, long strength, Vibration vibration) { return VibratorService.vibratorPerformEffect( diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java index 4c4dd8b8defa..4d9260aec62e 100644 --- a/services/core/java/com/android/server/am/UidObserverController.java +++ b/services/core/java/com/android/server/am/UidObserverController.java @@ -86,15 +86,17 @@ public class UidObserverController { } mService.mUiHandler.post(this::dispatchUidsChanged); } - final int NA = mAvailUidChanges.size(); - if (NA > 0) { - pendingChange = mAvailUidChanges.remove(NA-1); - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "Retrieving available item: " + pendingChange); + final int size = mAvailUidChanges.size(); + if (size > 0) { + pendingChange = mAvailUidChanges.remove(size - 1); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Retrieving available item: " + pendingChange); + } } else { pendingChange = new UidRecord.ChangeItem(); - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "Allocating new item: " + pendingChange); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Allocating new item: " + pendingChange); + } } if (uidRec != null) { uidRec.pendingChange = pendingChange; @@ -134,7 +136,8 @@ public class UidObserverController { } } pendingChange.change = change; - pendingChange.processState = uidRec != null ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT; + pendingChange.processState = uidRec != null + ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT; pendingChange.capability = uidRec != null ? uidRec.setCapability : 0; pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid); pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0; @@ -165,13 +168,13 @@ public class UidObserverController { @VisibleForTesting void dispatchUidsChanged() { - int N; + int numUidChanges; synchronized (mService) { - N = mPendingUidChanges.size(); - if (mActiveUidChanges.length < N) { - mActiveUidChanges = new UidRecord.ChangeItem[N]; + numUidChanges = mPendingUidChanges.size(); + if (mActiveUidChanges.length < numUidChanges) { + mActiveUidChanges = new UidRecord.ChangeItem[numUidChanges]; } - for (int i=0; i<N; i++) { + for (int i = 0; i < numUidChanges; i++) { final UidRecord.ChangeItem change = mPendingUidChanges.get(i); mActiveUidChanges[i] = change; if (change.uidRecord != null) { @@ -180,21 +183,22 @@ public class UidObserverController { } } mPendingUidChanges.clear(); - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "*** Delivering " + N + " uid changes"); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "*** Delivering " + numUidChanges + " uid changes"); + } } - mUidChangeDispatchCount += N; + mUidChangeDispatchCount += numUidChanges; int i = mUidObservers.beginBroadcast(); while (i > 0) { i--; dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i), - (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), N); + (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), numUidChanges); } mUidObservers.finishBroadcast(); if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) { - for (int j = 0; j < N; ++j) { + for (int j = 0; j < numUidChanges; ++j) { final UidRecord.ChangeItem item = mActiveUidChanges[j]; if ((item.change & UidRecord.CHANGE_GONE) != 0) { mValidateUids.remove(item.uid); @@ -217,7 +221,7 @@ public class UidObserverController { } synchronized (mService) { - for (int j = 0; j < N; j++) { + for (int j = 0; j < numUidChanges; j++) { mAvailUidChanges.add(mActiveUidChanges[j]); } } @@ -232,66 +236,72 @@ public class UidObserverController { for (int j = 0; j < changesSize; j++) { UidRecord.ChangeItem item = mActiveUidChanges[j]; final int change = item.change; - if (change == UidRecord.CHANGE_PROCSTATE && - (reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) { + if (change == UidRecord.CHANGE_PROCSTATE + && (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) { // No-op common case: no significant change, the observer is not // interested in all proc state changes. continue; } final long start = SystemClock.uptimeMillis(); if ((change & UidRecord.CHANGE_IDLE) != 0) { - if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "UID idle uid=" + item.uid); + if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "UID idle uid=" + item.uid); + } observer.onUidIdle(item.uid, item.ephemeral); } } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) { - if ((reg.which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "UID active uid=" + item.uid); + if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid); + } observer.onUidActive(item.uid); } } - if ((reg.which & ActivityManager.UID_OBSERVER_CACHED) != 0) { + if ((reg.mWhich & ActivityManager.UID_OBSERVER_CACHED) != 0) { if ((change & UidRecord.CHANGE_CACHED) != 0) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "UID cached uid=" + item.uid); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "UID cached uid=" + item.uid); + } observer.onUidCachedChanged(item.uid, true); } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "UID active uid=" + item.uid); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "UID active uid=" + item.uid); + } observer.onUidCachedChanged(item.uid, false); } } if ((change & UidRecord.CHANGE_GONE) != 0) { - if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "UID gone uid=" + item.uid); + if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "UID gone uid=" + item.uid); + } observer.onUidGone(item.uid, item.ephemeral); } - if (reg.lastProcStates != null) { - reg.lastProcStates.delete(item.uid); + if (reg.mLastProcStates != null) { + reg.mLastProcStates.delete(item.uid); } } else { - if ((reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { - if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, - "UID CHANGED uid=" + item.uid - + ": " + item.processState + ": " + item.capability); + if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "UID CHANGED uid=" + item.uid + + ": " + item.processState + ": " + item.capability); + } boolean doReport = true; - if (reg.cutpoint >= ActivityManager.MIN_PROCESS_STATE) { - final int lastState = reg.lastProcStates.get(item.uid, + if (reg.mCutpoint >= ActivityManager.MIN_PROCESS_STATE) { + final int lastState = reg.mLastProcStates.get(item.uid, ActivityManager.PROCESS_STATE_UNKNOWN); if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) { - final boolean lastAboveCut = lastState <= reg.cutpoint; - final boolean newAboveCut = item.processState <= reg.cutpoint; + final boolean lastAboveCut = lastState <= reg.mCutpoint; + final boolean newAboveCut = item.processState <= reg.mCutpoint; doReport = lastAboveCut != newAboveCut; } else { doReport = item.processState != PROCESS_STATE_NONEXISTENT; } } if (doReport) { - if (reg.lastProcStates != null) { - reg.lastProcStates.put(item.uid, item.processState); + if (reg.mLastProcStates != null) { + reg.mLastProcStates.put(item.uid, item.processState); } observer.onUidStateChanged(item.uid, item.processState, item.procStateSeq, item.capability); @@ -311,7 +321,7 @@ public class UidObserverController { } private boolean isEphemeralLocked(int uid) { - String packages[] = mService.mContext.getPackageManager().getPackagesForUid(uid); + final String[] packages = mService.mContext.getPackageManager().getPackagesForUid(uid); if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid return false; } @@ -321,41 +331,41 @@ public class UidObserverController { @GuardedBy("mService") void dump(PrintWriter pw, String dumpPackage) { - final int NI = mUidObservers.getRegisteredCallbackCount(); + final int count = mUidObservers.getRegisteredCallbackCount(); boolean printed = false; - for (int i=0; i<NI; i++) { + for (int i = 0; i < count; i++) { final UidObserverRegistration reg = (UidObserverRegistration) mUidObservers.getRegisteredCallbackCookie(i); - if (dumpPackage == null || dumpPackage.equals(reg.pkg)) { + if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) { if (!printed) { pw.println(" mUidObservers:"); printed = true; } - pw.print(" "); UserHandle.formatUid(pw, reg.uid); - pw.print(" "); pw.print(reg.pkg); + pw.print(" "); UserHandle.formatUid(pw, reg.mUid); + pw.print(" "); pw.print(reg.mPkg); final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i); pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":"); - if ((reg.which&ActivityManager.UID_OBSERVER_IDLE) != 0) { + if ((reg.mWhich & ActivityManager.UID_OBSERVER_IDLE) != 0) { pw.print(" IDLE"); } - if ((reg.which&ActivityManager.UID_OBSERVER_ACTIVE) != 0) { - pw.print(" ACT" ); + if ((reg.mWhich & ActivityManager.UID_OBSERVER_ACTIVE) != 0) { + pw.print(" ACT"); } - if ((reg.which&ActivityManager.UID_OBSERVER_GONE) != 0) { + if ((reg.mWhich & ActivityManager.UID_OBSERVER_GONE) != 0) { pw.print(" GONE"); } - if ((reg.which&ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { + if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { pw.print(" STATE"); - pw.print(" (cut="); pw.print(reg.cutpoint); + pw.print(" (cut="); pw.print(reg.mCutpoint); pw.print(")"); } pw.println(); - if (reg.lastProcStates != null) { - final int NJ = reg.lastProcStates.size(); - for (int j=0; j<NJ; j++) { + if (reg.mLastProcStates != null) { + final int size = reg.mLastProcStates.size(); + for (int j = 0; j < size; j++) { pw.print(" Last "); - UserHandle.formatUid(pw, reg.lastProcStates.keyAt(j)); - pw.print(": "); pw.println(reg.lastProcStates.valueAt(j)); + UserHandle.formatUid(pw, reg.mLastProcStates.keyAt(j)); + pw.print(": "); pw.println(reg.mLastProcStates.valueAt(j)); } } } @@ -366,8 +376,8 @@ public class UidObserverController { pw.print(mUidChangeDispatchCount); pw.println(); pw.println(" Slow UID dispatches:"); - final int N = mUidObservers.beginBroadcast(); - for (int i = 0; i < N; i++) { + final int size = mUidObservers.beginBroadcast(); + for (int i = 0; i < size; i++) { UidObserverRegistration r = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i); pw.print(" "); @@ -383,21 +393,21 @@ public class UidObserverController { @GuardedBy("mService") void dumpDebug(ProtoOutputStream proto, String dumpPackage) { - final int NI = mUidObservers.getRegisteredCallbackCount(); - for (int i=0; i<NI; i++) { + final int count = mUidObservers.getRegisteredCallbackCount(); + for (int i = 0; i < count; i++) { final UidObserverRegistration reg = (UidObserverRegistration) mUidObservers.getRegisteredCallbackCookie(i); - if (dumpPackage == null || dumpPackage.equals(reg.pkg)) { + if (dumpPackage == null || dumpPackage.equals(reg.mPkg)) { reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS); } } } - static final class UidObserverRegistration { - final int uid; - final String pkg; - final int which; - final int cutpoint; + private static final class UidObserverRegistration { + final int mUid; + final String mPkg; + final int mWhich; + final int mCutpoint; /** * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}. @@ -408,49 +418,49 @@ public class UidObserverController { /** Max time it took for each dispatch. */ int mMaxDispatchTime; - final SparseIntArray lastProcStates; + final SparseIntArray mLastProcStates; // Please keep the enum lists in sync - private static int[] ORIG_ENUMS = new int[]{ + private static final int[] ORIG_ENUMS = new int[]{ ActivityManager.UID_OBSERVER_IDLE, ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.UID_OBSERVER_GONE, ActivityManager.UID_OBSERVER_PROCSTATE, }; - private static int[] PROTO_ENUMS = new int[]{ + private static final int[] PROTO_ENUMS = new int[]{ ActivityManagerProto.UID_OBSERVER_FLAG_IDLE, ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE, ActivityManagerProto.UID_OBSERVER_FLAG_GONE, ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE, }; - UidObserverRegistration(int _uid, String _pkg, int _which, int _cutpoint) { - uid = _uid; - pkg = _pkg; - which = _which; - cutpoint = _cutpoint; + UidObserverRegistration(int uid, String pkg, int which, int cutpoint) { + this.mUid = uid; + this.mPkg = pkg; + this.mWhich = which; + this.mCutpoint = cutpoint; if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) { - lastProcStates = new SparseIntArray(); + mLastProcStates = new SparseIntArray(); } else { - lastProcStates = null; + mLastProcStates = null; } } void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - proto.write(UidObserverRegistrationProto.UID, uid); - proto.write(UidObserverRegistrationProto.PACKAGE, pkg); + proto.write(UidObserverRegistrationProto.UID, mUid); + proto.write(UidObserverRegistrationProto.PACKAGE, mPkg); ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS, - which, ORIG_ENUMS, PROTO_ENUMS); - proto.write(UidObserverRegistrationProto.CUT_POINT, cutpoint); - if (lastProcStates != null) { - final int NI = lastProcStates.size(); - for (int i=0; i<NI; i++) { + mWhich, ORIG_ENUMS, PROTO_ENUMS); + proto.write(UidObserverRegistrationProto.CUT_POINT, mCutpoint); + if (mLastProcStates != null) { + final int size = mLastProcStates.size(); + for (int i = 0; i < size; i++) { final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES); proto.write(UidObserverRegistrationProto.ProcState.UID, - lastProcStates.keyAt(i)); + mLastProcStates.keyAt(i)); proto.write(UidObserverRegistrationProto.ProcState.STATE, - lastProcStates.valueAt(i)); + mLastProcStates.valueAt(i)); proto.end(pToken); } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 295143e76235..29ee8eb13564 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4367,7 +4367,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // Flip state because app was explicitly added or removed to denylist. setMeteredNetworkDenylist(uid, (isDenylisted || isRestrictedByAdmin)); if (hasRule(oldRule, RULE_REJECT_METERED) && isAllowlisted) { - // Since dneylist prevails over allowlist, we need to handle the special case + // Since denylist prevails over allowlist, we need to handle the special case // where app is allowlisted and denylisted at the same time (although such // scenario should be blocked by the UI), then denylist is removed. setMeteredNetworkAllowlist(uid, isAllowlisted); diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp index 529fb8838aa1..b3f3a5e1ff72 100644 --- a/services/core/jni/com_android_server_VibratorService.cpp +++ b/services/core/jni/com_android_server_VibratorService.cpp @@ -181,6 +181,24 @@ static jintArray vibratorGetSupportedEffects(JNIEnv* env, jclass /* clazz */, jl return effects; } +static jintArray vibratorGetSupportedPrimitives(JNIEnv* env, jclass /* clazz */, + jlong controllerPtr) { + vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr); + if (controller == nullptr) { + ALOGE("vibratorGetSupportedPrimitives failed because controller was not initialized"); + return nullptr; + } + auto result = controller->getSupportedPrimitives(); + if (!result.isOk()) { + return nullptr; + } + std::vector<aidl::CompositePrimitive> supportedPrimitives = result.value(); + jintArray primitives = env->NewIntArray(supportedPrimitives.size()); + env->SetIntArrayRegion(primitives, 0, supportedPrimitives.size(), + reinterpret_cast<jint*>(supportedPrimitives.data())); + return primitives; +} + static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong effect, jlong strength, jobject vibration) { vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr); @@ -259,6 +277,7 @@ static const JNINativeMethod method_table[] = { "VibratorService$Vibration;)V", (void*)vibratorPerformComposedEffect}, {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects}, + {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives}, {"vibratorSetExternalControl", "(JZ)V", (void*)vibratorSetExternalControl}, {"vibratorGetCapabilities", "(J)J", (void*)vibratorGetCapabilities}, {"vibratorAlwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable}, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2ab629bf725a..6154bef2bda3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1579,21 +1579,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Creates a new {@link CallerIdentity} object to represent the caller's identity. + * The component name should be an active admin for the calling user. */ - private CallerIdentity getCallerIdentity(@NonNull ComponentName componentName) { + private CallerIdentity getCallerIdentity(@NonNull ComponentName adminComponent) { final int callerUid = mInjector.binderGetCallingUid(); final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid)); - ActiveAdmin admin = policy.mAdminMap.get(componentName); + ActiveAdmin admin = policy.mAdminMap.get(adminComponent); if (admin == null) { - throw new SecurityException(String.format("No active admin for %s", componentName)); + throw new SecurityException(String.format("No active admin for %s", adminComponent)); } if (admin.getUid() != callerUid) { throw new SecurityException( - String.format("Admin %s is not owned by uid %d", componentName, callerUid)); + String.format("Admin %s is not owned by uid %d", adminComponent, callerUid)); } - return new CallerIdentity(callerUid, componentName.getPackageName(), componentName); + return new CallerIdentity(callerUid, adminComponent.getPackageName(), adminComponent); } /** @@ -4589,12 +4590,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void enforceDeviceOwner(ComponentName who) { - synchronized (getLockObject()) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - } - } - private void enforceProfileOrDeviceOwner(ComponentName who) { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -5194,20 +5189,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty"); Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes"); + final CallerIdentity identity = getCallerIdentity(who); + // Remove possible duplicates. final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList)); // Ensure given scopes are valid. if (scopes.retainAll(Arrays.asList(DELEGATIONS))) { throw new IllegalArgumentException("Unexpected delegation scopes"); } - final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS); // Retrieve the user ID of the calling process. - final int userId = mInjector.userHandleGetCallingUserId(); + final int userId = identity.getUserId(); + final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS); synchronized (getLockObject()) { // Ensure calling process is device/profile owner. if (hasDoDelegation) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); } else { + // TODO move whole condition out of synchronized block getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); } // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N). @@ -6199,7 +6197,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) { - enforceDeviceOwner(who); + Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); mInjector.binderWithCleanCallingIdentity( () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo)); } @@ -6620,6 +6620,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); + // Allow setting this policy to true only if there is a split system user. if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) { throw new UnsupportedOperationException( @@ -6627,11 +6630,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } boolean removeAllUsers = false; synchronized (getLockObject()) { - final ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) { deviceOwner.forceEphemeralUsers = forceEphemeralUsers; - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + saveSettingsLocked(identity.getUserId()); mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers); removeAllUsers = forceEphemeralUsers; } @@ -6647,19 +6649,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - final ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - return deviceOwner.forceEphemeralUsers; - } - } + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); - private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) - throws SecurityException { synchronized (getLockObject()) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + return deviceOwner.forceEphemeralUsers; } - ensureAllUsersAffiliated(); } private void ensureAllUsersAffiliated() throws SecurityException { @@ -6676,11 +6672,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } Objects.requireNonNull(who, "ComponentName is null"); - // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport // which could still contain data related to that user. Should we disallow that, e.g. until // next boot? Might not be needed given that this still requires user consent. - ensureDeviceOwnerAndAllUsersAffiliated(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); + ensureAllUsersAffiliated(); if (mRemoteBugreportServiceIsActive.get() || (getDeviceOwnerRemoteBugreportUri() != null)) { @@ -8489,6 +8486,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setDefaultSmsApplication(ComponentName admin, String packageName, boolean parent) { Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); if (parent) { ActiveAdmin ap = getActiveAdminForCallerLocked(admin, @@ -8497,7 +8495,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage( packageName, getProfileParentId(mInjector.userHandleGetCallingUserId()))); } else { - enforceDeviceOwner(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); } mInjector.binderWithCleanCallingIdentity(() -> @@ -9259,14 +9257,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean removeUser(ComponentName who, UserHandle userHandle) { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); - final int callingUserId = mInjector.userHandleGetCallingUserId(); return mInjector.binderWithCleanCallingIdentity(() -> { String restriction = isManagedProfile(userHandle.getIdentifier()) ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER; - if (isAdminAffectedByRestriction(who, restriction, callingUserId)) { + if (isAdminAffectedByRestriction(who, restriction, identity.getUserId())) { Log.w(LOG_TAG, "The device owner cannot remove a user because " + restriction + " is enabled, and was not set by the device owner"); return false; @@ -9292,10 +9290,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean switchUser(ComponentName who, UserHandle userHandle) { Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); synchronized (getLockObject()) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - long id = mInjector.binderClearCallingIdentity(); try { int userId = UserHandle.USER_SYSTEM; @@ -9316,7 +9314,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public int startUserInBackground(ComponentName who, UserHandle userHandle) { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); final int userId = userHandle.getIdentifier(); if (isManagedProfile(userId)) { @@ -9348,7 +9347,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public int stopUser(ComponentName who, UserHandle userHandle) { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); final int userId = userHandle.getIdentifier(); if (isManagedProfile(userId)) { @@ -9416,7 +9416,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<UserHandle> getSecondaryUsers(ComponentName who) { Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); return mInjector.binderWithCleanCallingIdentity(() -> { final List<UserInfo> userInfos = mInjector.getUserManager().getUsers(true @@ -10378,6 +10379,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setGlobalSetting(ComponentName who, String setting, String value) { Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING) @@ -10386,8 +10389,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); synchronized (getLockObject()) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - // Some settings are no supported any more. However we do not want to throw a // SecurityException to avoid breaking apps. if (GLOBAL_SETTINGS_DEPRECATED.contains(setting)) { @@ -10468,7 +10469,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { - CallerIdentity identity = getCallerIdentity(who); + final CallerIdentity identity = getCallerIdentity(who); Preconditions.checkCallAuthorization(isDeviceOwner(identity)); mInjector.binderWithCleanCallingIdentity(() -> { @@ -12008,16 +12009,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isSystemOnlyUser(ComponentName admin) { - enforceDeviceOwner(admin); - final int callingUserId = mInjector.userHandleGetCallingUserId(); - return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM; + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); + return UserManager.isSplitSystemUser() && identity.getUserId() == UserHandle.USER_SYSTEM; } @Override public void reboot(ComponentName admin) { - Objects.requireNonNull(admin); - // Make sure caller has DO. - enforceDeviceOwner(admin); + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); + mInjector.binderWithCleanCallingIdentity(() -> { // Make sure there are no ongoing calls on the device. if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { @@ -13523,18 +13526,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } - Objects.requireNonNull(admin); + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); synchronized (getLockObject()) { - ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); if (deviceOwner.isLogoutEnabled == enabled) { // already in the requested state return; } deviceOwner.isLogoutEnabled = enabled; - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + saveSettingsLocked(identity.getUserId()); } } @@ -13700,20 +13703,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } - Objects.requireNonNull(admin); + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); final String startUserSessionMessageString = startUserSessionMessage != null ? startUserSessionMessage.toString() : null; synchronized (getLockObject()) { - final ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); if (TextUtils.equals(deviceOwner.startUserSessionMessage, startUserSessionMessage)) { return; } deviceOwner.startUserSessionMessage = startUserSessionMessageString; - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + saveSettingsLocked(identity.getUserId()); } mInjector.getActivityManagerInternal() @@ -13725,20 +13728,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return; } - Objects.requireNonNull(admin); + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); final String endUserSessionMessageString = endUserSessionMessage != null ? endUserSessionMessage.toString() : null; synchronized (getLockObject()) { - final ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); if (TextUtils.equals(deviceOwner.endUserSessionMessage, endUserSessionMessage)) { return; } deviceOwner.endUserSessionMessage = endUserSessionMessageString; - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + saveSettingsLocked(identity.getUserId()); } mInjector.getActivityManagerInternal() @@ -13750,11 +13753,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return null; } - Objects.requireNonNull(admin); + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); synchronized (getLockObject()) { - final ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); return deviceOwner.startUserSessionMessage; } } @@ -13764,11 +13768,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return null; } - Objects.requireNonNull(admin); + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(admin); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); synchronized (getLockObject()) { - final ActiveAdmin deviceOwner = - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); return deviceOwner.endUserSessionMessage; } } @@ -13807,9 +13812,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature || !mHasTelephonyFeature) { return -1; } - Objects.requireNonNull(who, "ComponentName is null in addOverrideApn"); + Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); if (tm != null) { @@ -13827,9 +13833,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature || !mHasTelephonyFeature) { return false; } - Objects.requireNonNull(who, "ComponentName is null in updateOverrideApn"); + Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); if (apnId < 0) { return false; @@ -13849,9 +13856,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature || !mHasTelephonyFeature) { return false; } - Objects.requireNonNull(who, "ComponentName is null in removeOverrideApn"); - enforceDeviceOwner(who); - + Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); return removeOverrideApnUnchecked(apnId); } @@ -13870,9 +13877,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature || !mHasTelephonyFeature) { return Collections.emptyList(); } - Objects.requireNonNull(who, "ComponentName is null in getOverrideApns"); - enforceDeviceOwner(who); - + Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); return getOverrideApnsUnchecked(); } @@ -13891,9 +13898,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature || !mHasTelephonyFeature) { return; } - Objects.requireNonNull(who, "ComponentName is null in setOverrideApnEnabled"); - enforceDeviceOwner(who); - + Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); setOverrideApnsEnabledUnchecked(enabled); } @@ -13909,8 +13916,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature || !mHasTelephonyFeature) { return false; } - Objects.requireNonNull(who, "ComponentName is null in isOverrideApnEnabled"); - enforceDeviceOwner(who); + Objects.requireNonNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity( () -> mContext.getContentResolver().query( @@ -13992,11 +14000,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return PRIVATE_DNS_SET_ERROR_FAILURE_SETTING; } - Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); - - final int returnCode; + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); switch (mode) { case PRIVATE_DNS_MODE_OPPORTUNISTIC: @@ -14030,9 +14036,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return PRIVATE_DNS_MODE_UNKNOWN; } - Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); + String currentMode = mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE); if (currentMode == null) { currentMode = ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK; @@ -14054,10 +14061,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return null; } - Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); - + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER); } @@ -14402,13 +14408,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setUserControlDisabledPackages(ComponentName who, List<String> packages) { - Preconditions.checkNotNull(who, "ComponentName is null"); + Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkNotNull(packages, "packages is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); - enforceDeviceOwner(who); synchronized (getLockObject()) { - final int userHandle = mInjector.userHandleGetCallingUserId(); - setUserControlDisabledPackagesLocked(userHandle, packages); + setUserControlDisabledPackagesLocked(identity.getUserId(), packages); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_USER_CONTROL_DISABLED_PACKAGES) .setAdmin(who) @@ -14428,12 +14434,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<String> getUserControlDisabledPackages(ComponentName who) { - Preconditions.checkNotNull(who, "ComponentName is null"); + final CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); - enforceDeviceOwner(who); - final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier(); synchronized (getLockObject()) { - final List<String> packages = getUserData(userHandle).mUserControlDisabledPackages; + final List<String> packages = + getUserData(identity.getUserId()).mUserControlDisabledPackages; return packages == null ? Collections.EMPTY_LIST : packages; } } diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index e4e7e2288590..4f636efc7c06 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -22,6 +22,7 @@ java_test_host { ], static_libs: [ "frameworks-base-hostutils", + "PackageManagerServiceHostTestsIntentVerifyUtils", ], test_suites: ["general-tests"], java_resources: [ @@ -33,7 +34,15 @@ java_test_host { ":PackageManagerTestAppVersion4", ":PackageManagerTestAppOriginalOverride", ":PackageManagerServiceDeviceSideTests", - ], + ":PackageManagerTestIntentVerifier", + ":PackageManagerTestIntentVerifierTarget1", + ":PackageManagerTestIntentVerifierTarget2", + ":PackageManagerTestIntentVerifierTarget3", + ":PackageManagerTestIntentVerifierTarget4Base", + ":PackageManagerTestIntentVerifierTarget4NoAutoVerify", + ":PackageManagerTestIntentVerifierTarget4Wildcard", + ":PackageManagerTestIntentVerifierTarget4WildcardNoAutoVerify", + ] } genrule { diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp new file mode 100644 index 000000000000..b7a0624e02b8 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/Android.bp @@ -0,0 +1,19 @@ +// 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. + +java_library { + name: "PackageManagerServiceHostTestsIntentVerifyUtils", + srcs: ["src/**/*.kt"], + host_supported: true, +} diff --git a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt index 09ef47212622..48119e0088c4 100644 --- a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java +++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/IntentVerifyTestParams.kt @@ -13,3 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +package com.android.server.pm.test.intent.verify + +interface IntentVerifyTestParams { + + val methodName: String + + fun toArgsMap(): Map<String, String> +} diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt new file mode 100644 index 000000000000..26c3903b20d6 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/SetActivityAsAlwaysParams.kt @@ -0,0 +1,43 @@ +/* + * 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.server.pm.test.intent.verify + +data class SetActivityAsAlwaysParams( + val uri: String, + val packageName: String, + val activityName: String, + override val methodName: String = "setActivityAsAlways" +) : IntentVerifyTestParams { + + companion object { + private const val KEY_URI = "uri" + private const val KEY_PACKAGE_NAME = "packageName" + private const val KEY_ACTIVITY_NAME = "activityName" + + fun fromArgs(args: Map<String, String>) = SetActivityAsAlwaysParams( + args.getValue(KEY_URI), + args.getValue(KEY_PACKAGE_NAME), + args.getValue(KEY_ACTIVITY_NAME) + ) + } + + override fun toArgsMap() = mapOf( + KEY_URI to uri, + KEY_PACKAGE_NAME to packageName, + KEY_ACTIVITY_NAME to activityName + ) +} diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt new file mode 100644 index 000000000000..7eddcfb621c4 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/StartActivityParams.kt @@ -0,0 +1,48 @@ +/* + * 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.server.pm.test.intent.verify + +data class StartActivityParams( + val uri: String, + val expected: List<String>, + val withBrowsers: Boolean = false, + override val methodName: String = "verifyActivityStart" +) : IntentVerifyTestParams { + companion object { + private const val KEY_URI = "uri" + private const val KEY_EXPECTED = "expected" + private const val KEY_BROWSER = "browser" + + fun fromArgs(args: Map<String, String>) = StartActivityParams( + args.getValue(KEY_URI), + args.getValue(KEY_EXPECTED).split(","), + args.getValue(KEY_BROWSER).toBoolean() + ) + } + + constructor( + uri: String, + expected: String, + withBrowsers: Boolean = false + ) : this(uri, listOf(expected), withBrowsers) + + override fun toArgsMap() = mapOf( + KEY_URI to uri, + KEY_EXPECTED to expected.joinToString(separator = ","), + KEY_BROWSER to withBrowsers.toString() + ) +} diff --git a/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt new file mode 100644 index 000000000000..f93b1e0e7e37 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/libs/IntentVerifyUtils/src/com/android/server/pm/test/intent/verify/VerifyRequest.kt @@ -0,0 +1,48 @@ +/* + * 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.server.pm.test.intent.verify + +data class VerifyRequest( + val id: Int = -1, + val scheme: String, + val hosts: List<String>, + val packageName: String +) { + + companion object { + fun deserialize(value: String?): VerifyRequest { + val lines = value?.trim()?.lines() + ?: return VerifyRequest(scheme = "", hosts = emptyList(), packageName = "") + return VerifyRequest( + lines[0].removePrefix("id=").toInt(), + lines[1].removePrefix("scheme="), + lines[2].removePrefix("hosts=").split(","), + lines[3].removePrefix("packageName=") + ) + } + } + + constructor(id: Int = -1, scheme: String, host: String, packageName: String) : + this(id, scheme, listOf(host), packageName) + + fun serializeToString() = """ + id=$id + scheme=$scheme + hosts=${hosts.joinToString(separator = ",")} + packageName=$packageName + """.trimIndent() + "\n" +} diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt index 3847658def6a..e17358d38d8c 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/FactoryPackageTest.kt @@ -31,7 +31,8 @@ class FactoryPackageTest : BaseHostJUnit4Test() { private val preparer: SystemPreparer = SystemPreparer(tempFolder, SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device } - @get:Rule + @Rule + @JvmField val rules = RuleChain.outerRule(tempFolder).around(preparer)!! private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.SYSTEM) diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt index 8dfefaf9750f..24c714c0d5f2 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt @@ -18,6 +18,7 @@ package com.android.server.pm.test import com.android.internal.util.test.SystemPreparer import com.android.tradefed.device.ITestDevice +import org.junit.rules.TemporaryFolder import java.io.File import java.io.FileOutputStream @@ -34,6 +35,19 @@ internal fun SystemPreparer.deleteApkFolders( } } +internal fun ITestDevice.installJavaResourceApk( + tempFolder: TemporaryFolder, + javaResource: String, + reinstall: Boolean = true, + extraArgs: Array<String> = emptyArray() +): String? { + val file = HostUtils.copyResourceToHostFile(javaResource, tempFolder.newFile()) + return installPackage(file, reinstall, *extraArgs) +} + +internal fun ITestDevice.uninstallPackages(vararg pkgNames: String) = + pkgNames.forEach { uninstallPackage(it) } + internal object HostUtils { fun getDataDir(device: ITestDevice, pkgName: String) = diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt index b7d135991ccd..37c999cbee68 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt @@ -47,7 +47,8 @@ class InvalidNewSystemAppTest : BaseHostJUnit4Test() { private val preparer: SystemPreparer = SystemPreparer(tempFolder, SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device } - @get:Rule + @Rule + @JvmField val rules = RuleChain.outerRule(tempFolder).around(preparer)!! private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT) diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt index 4ae3ca5f7263..4becae66633f 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OriginalPackageMigrationTest.kt @@ -47,7 +47,8 @@ class OriginalPackageMigrationTest : BaseHostJUnit4Test() { private val preparer: SystemPreparer = SystemPreparer(tempFolder, SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device } - @get:Rule + @Rule + @JvmField val rules = RuleChain.outerRule(tempFolder).around(preparer)!! @Before diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt index 654c11c5bf81..6479f584324f 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/Partition.kt @@ -22,6 +22,7 @@ import java.nio.file.Paths // Unfortunately no easy way to access PMS SystemPartitions, so mock them here internal enum class Partition(val baseAppFolder: Path) { SYSTEM("/system/app"), + SYSTEM_PRIVILEGED("/system/priv-app"), VENDOR("/vendor/app"), PRODUCT("/product/app"), SYSTEM_EXT("/system_ext/app") diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt index 207f10a3027b..46120af06550 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt @@ -110,7 +110,8 @@ class SystemStubMultiUserDisableUninstallTest : BaseHostJUnit4Test() { private val preparer: SystemPreparer = SystemPreparer(tempFolder, SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device } - @get:Rule + @Rule + @JvmField val rules = RuleChain.outerRule(tempFolder).let { if (DEBUG_NO_REBOOT) { it!! diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt new file mode 100644 index 000000000000..fffda8ebd36c --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/intent/verify/IntentFilterVerificationTest.kt @@ -0,0 +1,413 @@ +/* + * 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.server.pm.test.intent.verify + +import com.android.internal.util.test.SystemPreparer +import com.android.server.pm.test.Partition +import com.android.server.pm.test.deleteApkFolders +import com.android.server.pm.test.installJavaResourceApk +import com.android.server.pm.test.pushApk +import com.android.server.pm.test.uninstallPackages +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import java.io.File +import java.util.concurrent.TimeUnit + +@RunWith(DeviceJUnit4ClassRunner::class) +class IntentFilterVerificationTest : BaseHostJUnit4Test() { + + companion object { + private const val VERIFIER = "PackageManagerTestIntentVerifier.apk" + private const val VERIFIER_PKG_NAME = "com.android.server.pm.test.intent.verifier" + private const val TARGET_PKG_PREFIX = "$VERIFIER_PKG_NAME.target" + private const val TARGET_APK_PREFIX = "PackageManagerTestIntentVerifierTarget" + private const val TARGET_ONE = "${TARGET_APK_PREFIX}1.apk" + private const val TARGET_ONE_PKG_NAME = "$TARGET_PKG_PREFIX.one" + private const val TARGET_TWO = "${TARGET_APK_PREFIX}2.apk" + private const val TARGET_TWO_PKG_NAME = "$TARGET_PKG_PREFIX.two" + private const val TARGET_THREE = "${TARGET_APK_PREFIX}3.apk" + private const val TARGET_THREE_PKG_NAME = "$TARGET_PKG_PREFIX.three" + private const val TARGET_FOUR_BASE = "${TARGET_APK_PREFIX}4Base.apk" + private const val TARGET_FOUR_PKG_NAME = "$TARGET_PKG_PREFIX.four" + private const val TARGET_FOUR_NO_AUTO_VERIFY = "${TARGET_APK_PREFIX}4NoAutoVerify.apk" + private const val TARGET_FOUR_WILDCARD = "${TARGET_APK_PREFIX}4Wildcard.apk" + private const val TARGET_FOUR_WILDCARD_NO_AUTO_VERIFY = + "${TARGET_APK_PREFIX}4WildcardNoAutoVerify.apk" + + @get:ClassRule + val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) + } + + private val tempFolder = TemporaryFolder() + private val preparer: SystemPreparer = SystemPreparer(tempFolder, + SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device } + + @Rule + @JvmField + val rules = RuleChain.outerRule(tempFolder).around(preparer)!! + + private val permissionsFile = File("/system/etc/permissions" + + "/privapp-PackageManagerIntentFilterVerificationTest-permissions.xml") + + @Before + fun cleanupAndPushPermissionsFile() { + // In order for the test app to be the verification agent, it needs a permission file + // which can be pushed onto the system and removed afterwards. + val file = tempFolder.newFile().apply { + """ + <permissions> + <privapp-permissions package="$VERIFIER_PKG_NAME"> + <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/> + </privapp-permissions> + </permissions> + """ + .trimIndent() + .let { writeText(it) } + } + device.uninstallPackages(TARGET_ONE_PKG_NAME, TARGET_TWO_PKG_NAME, TARGET_THREE_PKG_NAME, + TARGET_FOUR_PKG_NAME) + preparer.pushApk(VERIFIER, Partition.SYSTEM_PRIVILEGED) + .pushFile(file, permissionsFile.toString()) + .reboot() + runTest("clearResponse") + } + + @After + fun cleanupAndDeletePermissionsFile() { + device.uninstallPackages(TARGET_ONE_PKG_NAME, TARGET_TWO_PKG_NAME, TARGET_THREE_PKG_NAME, + TARGET_FOUR_PKG_NAME) + preparer.deleteApkFolders(Partition.SYSTEM_PRIVILEGED, VERIFIER) + .deleteFile(permissionsFile.toString()) + device.reboot() + } + + @Test + fun verifyOne() { + installPackage(TARGET_ONE) + + assertReceivedRequests(true, VerifyRequest( + scheme = "https", + hosts = listOf( + "https_only.pm.server.android.com", + "other_activity.pm.server.android.com", + "http_only.pm.server.android.com", + "verify.pm.server.android.com", + "https_plus_non_web_scheme.pm.server.android.com", + "multiple.pm.server.android.com", + // TODO(b/159952358): the following domain should not be + // verified, this is because the verifier tries to verify all web domains, + // even in intent filters not marked for auto verify + "no_verify.pm.server.android.com" + ), + packageName = TARGET_ONE_PKG_NAME + )) + + runTest(StartActivityParams( + uri = "https://https_only.pm.server.android.com", + expected = "$TARGET_ONE_PKG_NAME.TargetActivity" + )) + } + + @Test + fun nonWebScheme() { + installPackage(TARGET_TWO) + assertReceivedRequests(null) + } + + @Test + fun verifyHttpNonSecureOnly() { + installPackage(TARGET_THREE) + assertReceivedRequests(true, VerifyRequest( + scheme = "https", + hosts = listOf( + "multiple.pm.server.android.com" + ), + packageName = TARGET_THREE_PKG_NAME + )) + + runTest(StartActivityParams( + uri = "http://multiple.pm.server.android.com", + expected = "$TARGET_THREE_PKG_NAME.TargetActivity" + )) + } + + @Test + fun multipleResults() { + installPackage(TARGET_ONE) + installPackage(TARGET_THREE) + assertReceivedRequests(true, VerifyRequest( + scheme = "https", + hosts = listOf( + "https_only.pm.server.android.com", + "other_activity.pm.server.android.com", + "http_only.pm.server.android.com", + "verify.pm.server.android.com", + "https_plus_non_web_scheme.pm.server.android.com", + "multiple.pm.server.android.com", + // TODO(b/159952358): the following domain should not be + // verified, this is because the verifier tries to verify all web domains, + // even in intent filters not marked for auto verify + "no_verify.pm.server.android.com" + ), + packageName = TARGET_ONE_PKG_NAME + ), VerifyRequest( + scheme = "https", + hosts = listOf( + "multiple.pm.server.android.com" + ), + packageName = TARGET_THREE_PKG_NAME + )) + + // Target3 declares http non-s, so it should be included in the set here + runTest(StartActivityParams( + uri = "http://multiple.pm.server.android.com", + expected = listOf( + "$TARGET_ONE_PKG_NAME.TargetActivity2", + "$TARGET_THREE_PKG_NAME.TargetActivity" + ) + )) + + // But it excludes https, so it shouldn't resolve here + runTest(StartActivityParams( + uri = "https://multiple.pm.server.android.com", + expected = "$TARGET_ONE_PKG_NAME.TargetActivity2" + )) + + // Remove Target3 and return to single verified Target1 app for http non-s + device.uninstallPackage(TARGET_THREE_PKG_NAME) + runTest(StartActivityParams( + uri = "http://multiple.pm.server.android.com", + expected = "$TARGET_ONE_PKG_NAME.TargetActivity2" + )) + } + + @Test + fun demoteAlways() { + installPackage(TARGET_FOUR_BASE) + assertReceivedRequests(false, VerifyRequest( + scheme = "https", + host = "failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME + )) + + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity", + withBrowsers = true + )) + runTest(SetActivityAsAlwaysParams( + uri = "https://failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME, + activityName = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + + // Re-installing with same host/verify set will maintain always setting + installPackage(TARGET_FOUR_BASE) + assertReceivedRequests(null) + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + + // Installing with new wildcard host will downgrade out of always, re-including browsers + installPackage(TARGET_FOUR_WILDCARD) + + // TODO(b/159952358): The first request without the wildcard should not be sent. This is + // caused by the request being queued even if it should be dropped from the previous + // install case since the host set didn't change. + assertReceivedRequests(false, VerifyRequest( + scheme = "https", + hosts = listOf("failing.pm.server.android.com"), + packageName = TARGET_FOUR_PKG_NAME + ), VerifyRequest( + scheme = "https", + hosts = listOf("failing.pm.server.android.com", "wildcard.tld"), + packageName = TARGET_FOUR_PKG_NAME + )) + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity", + withBrowsers = true + )) + } + + @Test + fun unverifiedReinstallResendRequest() { + installPackage(TARGET_FOUR_BASE) + assertReceivedRequests(false, VerifyRequest( + scheme = "https", + host = "failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME + )) + + installPackage(TARGET_FOUR_BASE) + + assertReceivedRequests(false, VerifyRequest( + scheme = "https", + host = "failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME + )) + } + + @Test + fun unverifiedUpdateRemovingDomainNoRequestDemoteAlways() { + installPackage(TARGET_FOUR_WILDCARD) + assertReceivedRequests(false, VerifyRequest( + scheme = "https", + hosts = listOf("failing.pm.server.android.com", "wildcard.tld"), + packageName = TARGET_FOUR_PKG_NAME + )) + + runTest(SetActivityAsAlwaysParams( + uri = "https://failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME, + activityName = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + + // Re-installing with a smaller host/verify set will not request re-verification + installPackage(TARGET_FOUR_BASE) + assertReceivedRequests(null) + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + + // Re-installing with a (now) larger host/verify set will re-request and demote + installPackage(TARGET_FOUR_WILDCARD) + // TODO(b/159952358): The first request should not be sent. This is caused by the request + // being queued even if it should be dropped from the previous install case. + assertReceivedRequests(false, VerifyRequest( + scheme = "https", + host = "failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME + ), VerifyRequest( + scheme = "https", + hosts = listOf("failing.pm.server.android.com", "wildcard.tld"), + packageName = TARGET_FOUR_PKG_NAME + )) + + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity", + withBrowsers = true + )) + } + + // TODO(b/159952358): I would expect this to demote + // TODO(b/32810168) + @Test + fun verifiedUpdateRemovingAutoVerifyMaintainsAlways() { + installPackage(TARGET_FOUR_BASE) + assertReceivedRequests(true, VerifyRequest( + scheme = "https", + host = "failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME + )) + + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + + installPackage(TARGET_FOUR_NO_AUTO_VERIFY) + assertReceivedRequests(null) + + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + } + + @Test + fun verifiedUpdateRemovingAutoVerifyAddingDomainDemotesAlways() { + installPackage(TARGET_FOUR_BASE) + + assertReceivedRequests(true, VerifyRequest( + scheme = "https", + host = "failing.pm.server.android.com", + packageName = TARGET_FOUR_PKG_NAME + )) + + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity" + )) + + installPackage(TARGET_FOUR_WILDCARD_NO_AUTO_VERIFY) + assertReceivedRequests(null) + + runTest(StartActivityParams( + uri = "https://failing.pm.server.android.com", + expected = "$TARGET_FOUR_PKG_NAME.TargetActivity", + withBrowsers = true + )) + } + + private fun installPackage(javaResourceName: String) { + // Need to pass --user as verification is not currently run for all user installs + assertThat(device.installJavaResourceApk(tempFolder, javaResourceName, + extraArgs = arrayOf("--user", device.currentUser.toString()))).isNull() + } + + private fun assertReceivedRequests(success: Boolean?, vararg expected: VerifyRequest?) { + // TODO(b/159952358): This can probably be less than 10 + // Because tests have to assert that multiple broadcasts aren't received, there's no real + // better way to await for a value than sleeping for a long enough time. + TimeUnit.SECONDS.sleep(10) + + val params = mutableMapOf<String, String>() + if (expected.any { it != null }) { + params["expected"] = expected.filterNotNull() + .joinToString(separator = "") { it.serializeToString() } + } + runTest("compareLastReceived", params) + + if (success != null) { + if (success) { + runTest("verifyPreviousReceivedSuccess") + } else { + runTest("verifyPreviousReceivedFailure") + } + runTest("clearResponse") + } + } + + private fun runTest(params: IntentVerifyTestParams) = + runTest(params.methodName, params.toArgsMap()) + + private fun runTest(testName: String, args: Map<String, String> = emptyMap()) { + val escapedArgs = args.mapValues { + // Need to escape strings so that args are passed properly through the shell command + "\"${it.value.trim('"')}\"" + } + runDeviceTests(device, null, VERIFIER_PKG_NAME, "$VERIFIER_PKG_NAME.VerifyReceiverTest", + testName, null, 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, escapedArgs) + } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp new file mode 100644 index 000000000000..e82f57d20fcc --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/Android.bp @@ -0,0 +1,28 @@ +// 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. + +android_test_helper_app { + name: "PackageManagerTestIntentVerifier", + srcs: [ "src/**/*.kt" ], + static_libs: [ + "androidx.test.core", + "androidx.test.espresso.core", + "androidx.test.runner", + "compatibility-device-util-axt", + "junit", + "truth-prebuilt", + "PackageManagerServiceHostTestsIntentVerifyUtils", + ], + platform_apis: true, +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml new file mode 100644 index 000000000000..17b50b0ec949 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier" + > + + <uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" /> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.pm.test.intent.verifier" + /> + + <application> + <receiver android:name=".VerifyReceiver" android:exported="true"> + <intent-filter android:priority="999"> + <action android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"/> + <data android:mimeType="application/vnd.android.package-archive"/> + </intent-filter> + </receiver> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt new file mode 100644 index 000000000000..073c2be75424 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiver.kt @@ -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.server.pm.test.intent.verifier + +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import com.android.server.pm.test.intent.verify.VerifyRequest + +class VerifyReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION) return + val params = intent.toVerifyParams() + + // If the receiver is called for a normal request, proxy it to the real verifier on device + if (params.hosts.none { it.contains("pm.server.android.com") }) { + sendToRealVerifier(context, Intent(intent)) + return + } + + // When the receiver is invoked for a test install, there is no direct connection to host, + // so store the result in a file to read and assert on later. Append is intentional so that + // amount of invocations and clean up can be verified. + context.filesDir.resolve("test.txt") + .appendText(params.serializeToString()) + } + + private fun sendToRealVerifier(context: Context, intent: Intent) { + context.packageManager.queryBroadcastReceivers(intent, 0) + .first { it.activityInfo?.packageName != context.packageName } + .let { it.activityInfo!! } + .let { intent.setComponent(ComponentName(it.packageName, it.name)) } + .run { context.sendBroadcast(intent) } + } + + private fun Intent.toVerifyParams() = VerifyRequest( + id = getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1), + scheme = getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME)!!, + hosts = getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS)!! + .split(' '), + packageName = getStringExtra( + PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME)!! + + ) +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt new file mode 100644 index 000000000000..6de3d4e160ec --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifier/src/com/android/server/pm/test/intent/verifier/VerifyReceiverTest.kt @@ -0,0 +1,160 @@ +/* + * 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.server.pm.test.intent.verifier + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.os.UserHandle +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import com.android.compatibility.common.util.ShellIdentityUtils +import com.android.server.pm.test.intent.verify.SetActivityAsAlwaysParams +import com.android.server.pm.test.intent.verify.StartActivityParams +import com.android.server.pm.test.intent.verify.VerifyRequest +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File + +@RunWith(AndroidJUnit4::class) +class VerifyReceiverTest { + + val args: Bundle = InstrumentationRegistry.getArguments() + val context: Context = InstrumentationRegistry.getContext() + + private val file = context.filesDir.resolve("test.txt") + + @Test + fun clearResponse() { + file.delete() + } + + @Test + fun compareLastReceived() { + val lastReceivedText = file.readTextIfExists() + val expectedText = args.getString("expected") + if (expectedText.isNullOrEmpty()) { + assertThat(lastReceivedText).isEmpty() + return + } + + val expectedParams = expectedText.parseParams() + val lastReceivedParams = lastReceivedText.parseParams() + + assertThat(lastReceivedParams).hasSize(expectedParams.size) + + lastReceivedParams.zip(expectedParams).forEach { (actual, expected) -> + assertThat(actual.hosts).containsExactlyElementsIn(expected.hosts) + assertThat(actual.packageName).isEqualTo(expected.packageName) + assertThat(actual.scheme).isEqualTo(expected.scheme) + } + } + + @Test + fun setActivityAsAlways() { + val params = SetActivityAsAlwaysParams.fromArgs( + args.keySet().associateWith { args.getString(it)!! }) + val uri = Uri.parse(params.uri) + val filter = IntentFilter().apply { + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme(uri.scheme) + addDataAuthority(uri.authority, null) + } + + val intent = Intent(Intent.ACTION_VIEW, uri).apply { + addCategory(Intent.CATEGORY_DEFAULT) + } + val allResults = context.packageManager.queryIntentActivities(intent, 0) + val allComponents = allResults + .map { ComponentName(it.activityInfo.packageName, it.activityInfo.name) } + .toTypedArray() + val matchingInfo = allResults.first { + it.activityInfo.packageName == params.packageName && + it.activityInfo.name == params.activityName + } + + ShellIdentityUtils.invokeMethodWithShellPermissions(context.packageManager, + ShellIdentityUtils.ShellPermissionMethodHelper<Unit, PackageManager> { + it.addUniquePreferredActivity(filter, matchingInfo.match, allComponents, + ComponentName(matchingInfo.activityInfo.packageName, + matchingInfo.activityInfo.name)) + it.updateIntentVerificationStatusAsUser(params.packageName, + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, + UserHandle.myUserId()) + }, "android.permission.SET_PREFERRED_APPLICATIONS") + } + + @Test + fun verifyPreviousReceivedSuccess() { + file.readTextIfExists() + .parseParams() + .forEach { + context.packageManager.verifyIntentFilter(it.id, + PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS, emptyList()) + } + } + + @Test + fun verifyPreviousReceivedFailure() { + file.readTextIfExists() + .parseParams() + .forEach { + context.packageManager.verifyIntentFilter(it.id, + PackageManager.INTENT_FILTER_VERIFICATION_FAILURE, it.hosts) + } + } + + @Test + fun verifyActivityStart() { + val params = StartActivityParams + .fromArgs(args.keySet().associateWith { args.getString(it)!! }) + val uri = Uri.parse(params.uri) + val intent = Intent(Intent.ACTION_VIEW).apply { + data = uri + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + val expectedActivities = params.expected.toMutableList() + + if (params.withBrowsers) { + // Since the host doesn't know what browsers the device has, query here and add it to + // set if it's expected that browser are returned + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com")) + expectedActivities += context.packageManager.queryIntentActivities(browserIntent, 0) + .map { it.activityInfo.name } + } + + val infos = context.packageManager.queryIntentActivities(intent, 0) + .map { it.activityInfo.name } + assertThat(infos).containsExactlyElementsIn(expectedActivities) + } + + private fun File.readTextIfExists() = if (exists()) readText() else "" + + // Rudimentary list deserialization by splitting text block into 4 line sections + private fun String.parseParams() = trim() + .lines() + .windowed(4, 4) + .map { it.joinToString(separator = "\n") } + .map { VerifyRequest.deserialize(it) } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp new file mode 100644 index 000000000000..7161fdd33516 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/Android.bp @@ -0,0 +1,48 @@ +// 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. + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget1", + manifest: "AndroidManifest1.xml", +} + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget2", + manifest: "AndroidManifest2.xml", +} + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget3", + manifest: "AndroidManifest3.xml", +} + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget4Base", + manifest: "AndroidManifest4Base.xml", +} + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget4NoAutoVerify", + manifest: "AndroidManifest4NoAutoVerify.xml", +} + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget4Wildcard", + manifest: "AndroidManifest4Wildcard.xml", +} + +android_test_helper_app { + name: "PackageManagerTestIntentVerifierTarget4WildcardNoAutoVerify", + manifest: "AndroidManifest4WildcardNoAutoVerify.xml", +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml new file mode 100644 index 000000000000..6cf5c7619a30 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest1.xml @@ -0,0 +1,94 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.one" android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="verify.pm.server.android.com" /> + </intent-filter> + + <intent-filter android:autoVerify="false"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="no_verify.pm.server.android.com" /> + </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:host="http_only.pm.server.android.com" /> + </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="https" /> + <data android:host="https_only.pm.server.android.com" /> + </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="htttps" /> + <data android:host="non_http.pm.server.android.com" /> + </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="https" /> + <data android:scheme="non_web_scheme" /> + <data android:host="https_plus_non_web_scheme.pm.server.android.com" /> + </intent-filter> + </activity> + + <activity android:name=".TargetActivity2" android:exported="true"> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="other_activity.pm.server.android.com" /> + </intent-filter> + + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="multiple.pm.server.android.com" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml new file mode 100644 index 000000000000..087ef70595f9 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest2.xml @@ -0,0 +1,34 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.two" + android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:scheme="non_web_scheme" /> + <data android:host="only_https_plus_non_web_scheme.pm.server.android.com" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml new file mode 100644 index 000000000000..eb75b5e53bc8 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest3.xml @@ -0,0 +1,32 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.three" + android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:host="multiple.pm.server.android.com" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml new file mode 100644 index 000000000000..7eacb8bc8fb7 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Base.xml @@ -0,0 +1,33 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.four" + android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="failing.pm.server.android.com" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml new file mode 100644 index 000000000000..ecfee55b9c4a --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4NoAutoVerify.xml @@ -0,0 +1,33 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.four" + android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="false"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="failing.pm.server.android.com" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml new file mode 100644 index 000000000000..0f0f53ba07e9 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4Wildcard.xml @@ -0,0 +1,34 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.four" + android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="failing.pm.server.android.com" /> + <data android:host="*.wildcard.tld" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml new file mode 100644 index 000000000000..d5652e1b924d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/IntentVerifierTarget/AndroidManifest4WildcardNoAutoVerify.xml @@ -0,0 +1,34 @@ +<?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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.intent.verifier.target.four" + android:versionCode="1"> + + <application> + <activity android:name=".TargetActivity" android:exported="true"> + <intent-filter android:autoVerify="false"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + <data android:host="failing.pm.server.android.com" /> + <data android:host="*.wildcard.tld" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java index 02d10e3b6ce1..b7a36f2eaed2 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java @@ -223,9 +223,24 @@ public class VibratorServiceTest { } @Test - public void arePrimitivesSupported_withComposeCapability_returnsAlwaysTrue() { + public void arePrimitivesSupported_withNullResultFromNative_returnsAlwaysFalse() { mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - assertArrayEquals(new boolean[]{true, true}, + when(mNativeWrapperMock.vibratorGetSupportedPrimitives()).thenReturn(null); + + assertArrayEquals(new boolean[]{false, false}, + createService().arePrimitivesSupported(new int[]{ + VibrationEffect.Composition.PRIMITIVE_CLICK, + VibrationEffect.Composition.PRIMITIVE_QUICK_RISE + })); + } + + @Test + public void arePrimitivesSupported_withSomeSupportedPrimitives_returnsBasedOnNativeResult() { + mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + when(mNativeWrapperMock.vibratorGetSupportedPrimitives()) + .thenReturn(new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}); + + assertArrayEquals(new boolean[]{true, false}, createService().arePrimitivesSupported(new int[]{ VibrationEffect.Composition.PRIMITIVE_CLICK, VibrationEffect.Composition.PRIMITIVE_QUICK_RISE diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 8fc228734f37..7f6723e88905 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -149,6 +149,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile"; public static final String NOT_ORG_OWNED_PROFILE_OWNER_MSG = "not the profile owner on organization-owned device"; + public static final String INVALID_CALLING_IDENTITY_MSG = "Calling identity is not authorized"; public static final String ONGOING_CALL_MSG = "ongoing call on the device"; // TODO replace all instances of this with explicit {@link #mServiceContext}. @@ -2404,13 +2405,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Set admin1 as DA. dpm.setActiveAdmin(admin1, false); assertTrue(dpm.isAdminActive(admin1)); - assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG, - () -> dpm.reboot(admin1)); + assertExpectException(SecurityException.class, /* messageRegex= */ + INVALID_CALLING_IDENTITY_MSG, () -> dpm.reboot(admin1)); // Set admin1 as PO. assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM)); - assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG, - () -> dpm.reboot(admin1)); + assertExpectException(SecurityException.class, /* messageRegex= */ + INVALID_CALLING_IDENTITY_MSG, () -> dpm.reboot(admin1)); // Remove PO and add DO. dpm.clearProfileOwner(admin1); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 0ea84da54b60..26d46dbd2ab7 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1276,7 +1276,8 @@ public class SoundTriggerService extends SystemService { * @return The initialized AudioRecord */ private @NonNull AudioRecord createAudioRecordForEvent( - @NonNull SoundTrigger.GenericRecognitionEvent event) { + @NonNull SoundTrigger.GenericRecognitionEvent event) + throws IllegalArgumentException, UnsupportedOperationException { AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD); AudioAttributes attributes = attributesBuilder.build(); @@ -1285,21 +1286,15 @@ public class SoundTriggerService extends SystemService { sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent")); - try { - return (new AudioRecord.Builder()) - .setAudioAttributes(attributes) - .setAudioFormat((new AudioFormat.Builder()) - .setChannelMask(originalFormat.getChannelMask()) - .setEncoding(originalFormat.getEncoding()) - .setSampleRate(originalFormat.getSampleRate()) - .build()) - .setSessionId(event.getCaptureSession()) - .build(); - } catch (IllegalArgumentException | UnsupportedOperationException e) { - Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event - + "), failed to create AudioRecord"); - return null; - } + return (new AudioRecord.Builder()) + .setAudioAttributes(attributes) + .setAudioFormat((new AudioFormat.Builder()) + .setChannelMask(originalFormat.getChannelMask()) + .setEncoding(originalFormat.getEncoding()) + .setSampleRate(originalFormat.getSampleRate()) + .build()) + .setSessionId(event.getCaptureSession()) + .build(); } @Override @@ -1325,13 +1320,13 @@ public class SoundTriggerService extends SystemService { // execute if throttled: () -> { if (event.isCaptureAvailable()) { - AudioRecord capturedData = createAudioRecordForEvent(event); - - // Currently we need to start and release the audio record to reset - // the DSP even if we don't want to process the event - if (capturedData != null) { + try { + AudioRecord capturedData = createAudioRecordForEvent(event); capturedData.startRecording(); capturedData.release(); + } catch (IllegalArgumentException | UnsupportedOperationException e) { + Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event + + "), failed to create AudioRecord"); } } })); diff --git a/tests/AutoVerify/app1/Android.bp b/tests/AutoVerify/app1/Android.bp deleted file mode 100644 index 548519fa653b..000000000000 --- a/tests/AutoVerify/app1/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -android_app { - name: "AutoVerifyTest", - srcs: ["src/**/*.java"], - resource_dirs: ["res"], - platform_apis: true, - min_sdk_version: "26", - target_sdk_version: "26", - optimize: { - enabled: false, - }, -} diff --git a/tests/AutoVerify/app1/AndroidManifest.xml b/tests/AutoVerify/app1/AndroidManifest.xml deleted file mode 100644 index d9caad490d82..000000000000 --- a/tests/AutoVerify/app1/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.test.autoverify" > - - <uses-sdk android:targetSdkVersion="26" /> - - <application - android:label="@string/app_name" > - <activity - android:name=".MainActivity" - android:label="@string/app_name" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <intent-filter android:autoVerify="true"> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - <data android:scheme="http" /> - <data android:scheme="https" /> - <data android:host="explicit.example.com" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/tests/AutoVerify/app1/res/values/strings.xml b/tests/AutoVerify/app1/res/values/strings.xml deleted file mode 100644 index e234355041c6..000000000000 --- a/tests/AutoVerify/app1/res/values/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 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. ---> - -<resources> - <!-- app icon label, do not translate --> - <string name="app_name" translatable="false">AutoVerify Test</string> -</resources> diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java deleted file mode 100644 index 09ef47212622..000000000000 --- a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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. - */ diff --git a/tests/AutoVerify/app2/Android.bp b/tests/AutoVerify/app2/Android.bp deleted file mode 100644 index 1c6c97bdf350..000000000000 --- a/tests/AutoVerify/app2/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -android_app { - name: "AutoVerifyTest2", - srcs: ["src/**/*.java"], - resource_dirs: ["res"], - platform_apis: true, - min_sdk_version: "26", - target_sdk_version: "26", - optimize: { - enabled: false, - }, -} diff --git a/tests/AutoVerify/app2/AndroidManifest.xml b/tests/AutoVerify/app2/AndroidManifest.xml deleted file mode 100644 index a00807883cfc..000000000000 --- a/tests/AutoVerify/app2/AndroidManifest.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.test.autoverify" > - - <uses-sdk android:targetSdkVersion="26" /> - - <application - android:label="@string/app_name" > - <activity - android:name=".MainActivity" - android:label="@string/app_name" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <intent-filter android:autoVerify="true"> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - <data android:scheme="http" /> - <data android:scheme="https" /> - <data android:host="explicit.example.com" /> - <data android:host="*.wildcard.tld" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/tests/AutoVerify/app2/res/values/strings.xml b/tests/AutoVerify/app2/res/values/strings.xml deleted file mode 100644 index e234355041c6..000000000000 --- a/tests/AutoVerify/app2/res/values/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 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. ---> - -<resources> - <!-- app icon label, do not translate --> - <string name="app_name" translatable="false">AutoVerify Test</string> -</resources> diff --git a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java deleted file mode 100644 index 09ef47212622..000000000000 --- a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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. - */ diff --git a/tests/AutoVerify/app3/Android.bp b/tests/AutoVerify/app3/Android.bp deleted file mode 100644 index 70a2b77d1000..000000000000 --- a/tests/AutoVerify/app3/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -android_app { - name: "AutoVerifyTest3", - srcs: ["src/**/*.java"], - resource_dirs: ["res"], - platform_apis: true, - min_sdk_version: "26", - target_sdk_version: "26", - optimize: { - enabled: false, - }, -} diff --git a/tests/AutoVerify/app3/AndroidManifest.xml b/tests/AutoVerify/app3/AndroidManifest.xml deleted file mode 100644 index efaabc9a38d3..000000000000 --- a/tests/AutoVerify/app3/AndroidManifest.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.test.autoverify" > - - <uses-sdk android:targetSdkVersion="26" /> - - <application - android:label="@string/app_name" > - <activity - android:name=".MainActivity" - android:label="@string/app_name" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <!-- does not request autoVerify --> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - <data android:scheme="http" /> - <data android:scheme="https" /> - <data android:host="explicit.example.com" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/tests/AutoVerify/app3/res/values/strings.xml b/tests/AutoVerify/app3/res/values/strings.xml deleted file mode 100644 index e234355041c6..000000000000 --- a/tests/AutoVerify/app3/res/values/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 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. ---> - -<resources> - <!-- app icon label, do not translate --> - <string name="app_name" translatable="false">AutoVerify Test</string> -</resources> diff --git a/tests/AutoVerify/app4/Android.bp b/tests/AutoVerify/app4/Android.bp deleted file mode 100644 index fbdae1181a7a..000000000000 --- a/tests/AutoVerify/app4/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -android_app { - name: "AutoVerifyTest4", - srcs: ["src/**/*.java"], - resource_dirs: ["res"], - platform_apis: true, - min_sdk_version: "26", - target_sdk_version: "26", - optimize: { - enabled: false, - }, -} diff --git a/tests/AutoVerify/app4/AndroidManifest.xml b/tests/AutoVerify/app4/AndroidManifest.xml deleted file mode 100644 index 1c975f8336c9..000000000000 --- a/tests/AutoVerify/app4/AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.test.autoverify" > - - <uses-sdk android:targetSdkVersion="26" /> - - <application - android:label="@string/app_name" > - <activity - android:name=".MainActivity" - android:label="@string/app_name" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <!-- intentionally does not autoVerify --> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - <data android:scheme="http" /> - <data android:scheme="https" /> - <data android:host="explicit.example.com" /> - <data android:host="*.wildcard.tld" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/tests/AutoVerify/app4/res/values/strings.xml b/tests/AutoVerify/app4/res/values/strings.xml deleted file mode 100644 index e234355041c6..000000000000 --- a/tests/AutoVerify/app4/res/values/strings.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 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. ---> - -<resources> - <!-- app icon label, do not translate --> - <string name="app_name" translatable="false">AutoVerify Test</string> -</resources> diff --git a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java deleted file mode 100644 index 09ef47212622..000000000000 --- a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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. - */ diff --git a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java index c2a5459ae125..f80af034fa2b 100644 --- a/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java +++ b/tests/utils/hostutils/src/com/android/internal/util/test/SystemPreparer.java @@ -62,12 +62,22 @@ public class SystemPreparer extends ExternalResource { private final RebootStrategy mRebootStrategy; private final TearDownRule mTearDownRule; + // When debugging, it may be useful to run a test case without rebooting the device afterwards, + // to manually verify the device state. + private boolean mDebugSkipAfterReboot; + public SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) { this(hostTempFolder, RebootStrategy.FULL, null, deviceProvider); } public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy, @Nullable TestRuleDelegate testRuleDelegate, DeviceProvider deviceProvider) { + this(hostTempFolder, rebootStrategy, testRuleDelegate, false, deviceProvider); + } + + public SystemPreparer(TemporaryFolder hostTempFolder, RebootStrategy rebootStrategy, + @Nullable TestRuleDelegate testRuleDelegate, boolean debugSkipAfterReboot, + DeviceProvider deviceProvider) { mHostTempFolder = hostTempFolder; mDeviceProvider = deviceProvider; mRebootStrategy = rebootStrategy; @@ -75,6 +85,7 @@ public class SystemPreparer extends ExternalResource { if (testRuleDelegate != null) { testRuleDelegate.setDelegate(mTearDownRule); } + mDebugSkipAfterReboot = debugSkipAfterReboot; } /** Copies a file within the host test jar to a path on device. */ @@ -172,7 +183,9 @@ public class SystemPreparer extends ExternalResource { case USERSPACE_UNTIL_ONLINE: device.rebootUserspaceUntilOnline(); break; - case START_STOP: + // TODO(b/159540015): Make this START_STOP instead of default once it's fixed. Can't + // currently be done because START_STOP is commented out. + default: device.executeShellCommand("stop"); device.executeShellCommand("start"); ITestDevice.RecoveryMode cachedRecoveryMode = device.getRecoveryMode(); @@ -228,7 +241,9 @@ public class SystemPreparer extends ExternalResource { for (final String packageName : mInstalledPackages) { device.uninstallPackage(packageName); } - reboot(); + if (!mDebugSkipAfterReboot) { + reboot(); + } } catch (DeviceNotAvailableException e) { Assert.fail(e.toString()); } @@ -355,6 +370,7 @@ public class SystemPreparer extends ExternalResource { /** * How to reboot the device. Ordered from slowest to fastest. */ + @SuppressWarnings("DanglingJavadoc") public enum RebootStrategy { /** @see ITestDevice#reboot() */ FULL, @@ -374,7 +390,15 @@ public class SystemPreparer extends ExternalResource { * * TODO(b/159540015): There's a bug with this causing unnecessary disk space usage, which * can eventually lead to an insufficient storage space error. + * + * This can be uncommented for local development, but should be left out when merging. + * It is done this way to hopefully be caught by code review, since merging this will + * break all of postsubmit. But the nearly 50% reduction in test runtime is worth having + * this option exist. + * + * @deprecated do not use this in merged code until bug is resolved */ - START_STOP +// @Deprecated +// START_STOP } } |