Merge "Adds StatusBarNotificationActivityStarterTest"

This commit is contained in:
Mark Renouf
2019-03-25 01:44:48 +00:00
committed by Android (Google) Code Review
8 changed files with 387 additions and 82 deletions

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import java.util.List;
/**
* Contains useful methods for querying properties of an Activity Intent.
*/
public class ActivityIntentHelper {
private final Context mContext;
public ActivityIntentHelper(Context context) {
mContext = context;
}
/**
* Determines if sending the given intent would result in starting an Intent resolver activity,
* instead of resolving to a specific component.
*
* @param intent the intent
* @param currentUserId the id for the user to resolve as
* @return true if the intent would launch a resolver activity
*/
public boolean wouldLaunchResolverActivity(Intent intent, int currentUserId) {
ActivityInfo targetActivityInfo = getTargetActivityInfo(intent, currentUserId,
false /* onlyDirectBootAware */);
return targetActivityInfo == null;
}
/**
* Returns info about the target Activity of a given intent, or null if the intent does not
* resolve to a specific component meeting the requirements.
*
* @param onlyDirectBootAware a boolean indicating whether the matched activity packages must
* be direct boot aware when in direct boot mode if false, all packages are considered
* a match even if they are not aware.
* @return the target activity info of the intent it resolves to a specific package or
* {@code null} if it resolved to the resolver activity
*/
public ActivityInfo getTargetActivityInfo(Intent intent, int currentUserId,
boolean onlyDirectBootAware) {
PackageManager packageManager = mContext.getPackageManager();
int flags = PackageManager.MATCH_DEFAULT_ONLY;
if (!onlyDirectBootAware) {
flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
}
final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
intent, flags, currentUserId);
if (appList.size() == 0) {
return null;
}
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
flags | PackageManager.GET_META_DATA, currentUserId);
if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) {
return null;
} else {
return resolved.activityInfo;
}
}
/**
* Determines if the given intent resolves to an Activity which is allowed to appear above
* the lock screen.
*
* @param intent the intent to resolve
* @return true if the launched Activity would appear above the lock screen
*/
public boolean wouldShowOverLockscreen(Intent intent, int currentUserId) {
ActivityInfo targetActivityInfo = getTargetActivityInfo(intent,
currentUserId, false /* onlyDirectBootAware */);
return targetActivityInfo != null
&& (targetActivityInfo.flags & (ActivityInfo.FLAG_SHOW_WHEN_LOCKED
| ActivityInfo.FLAG_SHOW_FOR_ALL_USERS)) > 0;
}
/**
* Determines if sending the given intent would result in starting an Intent resolver activity,
* instead of resolving to a specific component.
*
* @param resolved the resolveInfo for the intent as returned by resolveActivityAsUser
* @param appList a list of resolveInfo as returned by queryIntentActivitiesAsUser
* @return true if the intent would launch a resolver activity
*/
public boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
// If the list contains the above resolved activity, then it can't be
// ResolverActivity itself.
for (int i = 0; i < appList.size(); i++) {
ResolveInfo tmp = appList.get(i);
if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
&& tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
return false;
}
}
return true;
}
}

View File

@@ -67,6 +67,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -170,6 +171,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private boolean mDozing;
private int mIndicationBottomMargin;
private float mDarkAmount;
private ActivityIntentHelper mActivityIntentHelper;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -235,6 +237,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
protected void onFinishInflate() {
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
new ActivityIntentHelper(mContext));
mPreviewContainer = findViewById(R.id.preview_container);
mOverlayContainer = findViewById(R.id.overlay_container);
mRightAffordanceView = findViewById(R.id.camera_button);
@@ -254,7 +258,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mLockIcon.update();
setClipChildren(false);
setClipToPadding(false);
mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
inflateCameraPreview();
mLockIcon.setOnClickListener(this);
mLockIcon.setOnLongClickListener(this);
@@ -266,6 +269,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mAccessibilityController = Dependency.get(AccessibilityController.class);
mAssistManager = Dependency.get(AssistManager.class);
mLockIcon.setAccessibilityController(mAccessibilityController);
mActivityIntentHelper = new ActivityIntentHelper(getContext());
updateLeftAffordance();
}
@@ -459,7 +463,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void bindCameraPrewarmService() {
Intent intent = getCameraIntent();
ActivityInfo targetInfo = PreviewInflater.getTargetActivityInfo(mContext, intent,
ActivityInfo targetInfo = mActivityIntentHelper.getTargetActivityInfo(intent,
KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
if (targetInfo != null && targetInfo.metaData != null) {
String clazz = targetInfo.metaData.getString(
@@ -500,8 +504,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void launchCamera(String source) {
final Intent intent = getCameraIntent();
intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
boolean wouldLaunchResolverActivity = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser());
if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
AsyncTask.execute(new Runnable() {
@Override

View File

@@ -25,6 +25,7 @@ import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static com.android.systemui.Dependency.MAIN_HANDLER;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -126,6 +127,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
import com.android.systemui.DemoMode;
@@ -219,7 +221,6 @@ import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
@@ -586,11 +587,12 @@ public class StatusBar extends SystemUI implements DemoMode,
mEntryManager.updateNotifications();
updateScrimController();
};
private ActivityIntentHelper mActivityIntentHelper;
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
mForegroundServiceController.onAppOpChanged(code, uid, packageName, active);
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
Dependency.get(MAIN_HANDLER).post(() -> {
mNotificationListController.updateNotificationsForAppOp(code, uid, packageName, active);
});
}
@@ -635,6 +637,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNavigationBarController = Dependency.get(NavigationBarController.class);
mBubbleController = Dependency.get(BubbleController.class);
mBubbleController.setExpandListener(mBubbleExpandListener);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
sliceProvider.initDependencies(mMediaManager, mStatusBarStateController);
@@ -1069,7 +1072,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mRemoteInputManager, mStatusBarRemoteInputCallback, mGroupManager,
mLockscreenUserManager, shadeController, mKeyguardMonitor,
mNotificationInterruptionStateProvider, mMetricsLogger,
new LockPatternUtils(mContext));
new LockPatternUtils(mContext), Dependency.get(MAIN_HANDLER),
mActivityIntentHelper);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -2449,8 +2453,8 @@ public class StatusBar extends SystemUI implements DemoMode,
final Callback callback, int flags) {
if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return;
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, mLockscreenUserManager.getCurrentUserId());
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, mLockscreenUserManager.getCurrentUserId());
Runnable runnable = () -> {
mAssistManager.hideAssist();
intent.setFlags(
@@ -4315,7 +4319,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void startPendingIntentDismissingKeyguard(
final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback) {
final boolean afterKeyguardGone = intent.isActivity()
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
executeActionDismissingKeyguard(() -> {

View File

@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.Dependency.MAIN_HANDLER;
import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
import android.app.ActivityManager;
@@ -29,6 +28,7 @@ import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -43,6 +43,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
import com.android.systemui.UiOffloadThread;
@@ -65,7 +66,6 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.PreviewInflater;
/**
* Status bar implementation of {@link NotificationActivityStarter}.
@@ -97,6 +97,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final IStatusBarService mBarService;
private final CommandQueue mCommandQueue;
private final IDreamManager mDreamManager;
private final Handler mMainThreadHandler;
private final ActivityIntentHelper mActivityIntentHelper;
private boolean mIsCollapsingToShowActivityOverLockscreen;
@@ -121,7 +123,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
KeyguardMonitor keyguardMonitor,
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils) {
LockPatternUtils lockPatternUtils,
Handler mainThreadHandler,
ActivityIntentHelper activityIntentHelper) {
mContext = context;
mNotificationPanel = panel;
mPresenter = presenter;
@@ -150,6 +154,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
});
mStatusBarRemoteInputCallback = remoteInputCallback;
mMainThreadHandler = mainThreadHandler;
mActivityIntentHelper = activityIntentHelper;
}
/**
@@ -176,12 +182,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent = intent.isActivity();
final boolean afterKeyguardGone = isActivityIntent
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
final boolean wasOccluded = mShadeController.isOccluded();
boolean showOverLockscreen = mKeyguardMonitor.isShowing()
&& PreviewInflater.wouldShowOverLockscreen(mContext,
intent.getIntent(),
&& mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
ActivityStarter.OnDismissAction postKeyguardAction =
() -> handleNotificationClickAfterKeyguardDismissed(
@@ -358,7 +363,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */);
if (shouldCollapse()) {
// Putting it back on the main thread, since we're touching views
Dependency.get(MAIN_HANDLER).post(() -> mCommandQueue.animateCollapsePanels(
mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
}
});
@@ -425,7 +430,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
if (Looper.getMainLooper().isCurrentThread()) {
mShadeController.collapsePanel();
} else {
Dependency.get(MAIN_HANDLER).post(mShadeController::collapsePanel);
mMainThreadHandler.post(mShadeController::collapsePanel);
}
}
@@ -444,7 +449,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private void removeNotification(StatusBarNotification notification) {
// We have to post it to the UI thread for synchronization
Dependency.get(MAIN_HANDLER).post(() -> {
mMainThreadHandler.post(() -> {
Runnable removeRunnable =
() -> mEntryManager.performRemoveNotification(notification);
if (mPresenter.isCollapsing()) {

View File

@@ -33,6 +33,7 @@ import android.os.UserHandle;
import android.view.View;
import android.view.ViewParent;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -46,7 +47,6 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.PreviewInflater;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -64,6 +64,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
Dependency.get(NotificationLockscreenUserManager.class);
private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
private final Context mContext;
private final ActivityIntentHelper mActivityIntentHelper;
private View mPendingWorkRemoteInputView;
private View mPendingRemoteInputView;
private final ShadeController mShadeController = Dependency.get(ShadeController.class);
@@ -83,6 +84,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mCommandQueue = getComponent(context, CommandQueue.class);
mCommandQueue.addCallback(this);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
}
@Override
@@ -220,8 +222,8 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
NotificationRemoteInputManager.ClickHandler defaultHandler) {
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
mActivityStarter.dismissKeyguardThenExecute(() -> {
try {
ActivityManager.getService().resumeAppSwitches();

View File

@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.policy;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
@@ -29,6 +28,7 @@ import android.view.View;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.statusbar.phone.KeyguardPreviewContainer;
import java.util.List;
@@ -41,13 +41,16 @@ public class PreviewInflater {
private static final String TAG = "PreviewInflater";
private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
private final ActivityIntentHelper mActivityIntentHelper;
private Context mContext;
private LockPatternUtils mLockPatternUtils;
public PreviewInflater(Context context, LockPatternUtils lockPatternUtils) {
public PreviewInflater(Context context, LockPatternUtils lockPatternUtils,
ActivityIntentHelper activityIntentHelper) {
mContext = context;
mLockPatternUtils = lockPatternUtils;
mActivityIntentHelper = activityIntentHelper;
}
public View inflatePreview(Intent intent) {
@@ -130,7 +133,7 @@ public class PreviewInflater {
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
flags | PackageManager.GET_META_DATA,
KeyguardUpdateMonitor.getCurrentUser());
if (wouldLaunchResolverActivity(resolved, appList)) {
if (mActivityIntentHelper.wouldLaunchResolverActivity(resolved, appList)) {
return null;
}
if (resolved == null || resolved.activityInfo == null) {
@@ -140,63 +143,6 @@ public class PreviewInflater {
resolved.activityInfo.metaData);
}
public static boolean wouldLaunchResolverActivity(Context ctx, Intent intent,
int currentUserId) {
return getTargetActivityInfo(ctx, intent, currentUserId, false /* onlyDirectBootAware */)
== null;
}
public static boolean wouldShowOverLockscreen(Context ctx, Intent intent, int currentUserId) {
ActivityInfo targetActivityInfo = getTargetActivityInfo(ctx, intent, currentUserId,
false /* onlyDirectBootAware */);
return targetActivityInfo != null
&& (targetActivityInfo.flags & (ActivityInfo.FLAG_SHOW_WHEN_LOCKED
| ActivityInfo.FLAG_SHOW_FOR_ALL_USERS)) > 0;
}
/**
* @param onlyDirectBootAware a boolean indicating whether the matched activity packages must
* be direct boot aware when in direct boot mode if false, all
* packages are considered a match even if they are not aware.
* @return the target activity info of the intent it resolves to a specific package or
* {@code null} if it resolved to the resolver activity
*/
public static ActivityInfo getTargetActivityInfo(Context ctx, Intent intent,
int currentUserId, boolean onlyDirectBootAware) {
PackageManager packageManager = ctx.getPackageManager();
int flags = PackageManager.MATCH_DEFAULT_ONLY;
if (!onlyDirectBootAware) {
flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
}
final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
intent, flags, currentUserId);
if (appList.size() == 0) {
return null;
}
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
flags | PackageManager.GET_META_DATA, currentUserId);
if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) {
return null;
} else {
return resolved.activityInfo;
}
}
private static boolean wouldLaunchResolverActivity(
ResolveInfo resolved, List<ResolveInfo> appList) {
// If the list contains the above resolved activity, then it can't be
// ResolverActivity itself.
for (int i = 0; i < appList.size(); i++) {
ResolveInfo tmp = appList.get(i);
if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
&& tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
return false;
}
}
return true;
}
private static class WidgetInfo {
String contextPackage;
int layoutId;

View File

@@ -35,6 +35,7 @@ import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -89,6 +90,8 @@ public class BubbleControllerTest extends SysuiTestCase {
private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener;
@Mock
private BubbleController.BubbleExpandListener mBubbleExpandListener;
@Mock
NotificationVisibility mNotificationVisibility;
@Mock
private PendingIntent mDeleteIntent;

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone;
import static org.mockito.AdditionalAnswers.answerVoid;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Mock
private AssistManager mAssistManager;
@Mock
private NotificationEntryManager mEntryManager;
@Mock
private ActivityStarter mActivityStarter;
@Mock
private IStatusBarService mStatusBarService;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private NotificationRemoteInputManager mRemoteInputManager;
@Mock
private RemoteInputController mRemoteInputController;
@Mock
private ShadeController mShadeController;
@Mock
private KeyguardMonitor mKeyguardMonitor;
@Mock
private Handler mHandler;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@Mock
private PendingIntent mContentIntent;
@Mock
private NotificationData mNotificationData;
@Mock
private NotificationEntry mNotificationEntry;
@Mock
private NotificationEntry mBubbleEntry;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationTestHelper mNotificationTestHelper;
ExpandableNotificationRow mNotificationRow;
private final Answer<Void> mCallOnDismiss = answerVoid(
(ActivityStarter.OnDismissAction dismissAction, Runnable cancel,
Boolean afterKeyguardGone) -> dismissAction.onDismiss());
private ArrayList<NotificationEntry> mActiveNotifications;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
mActiveNotifications = new ArrayList<>();
mActiveNotifications.add(mNotificationEntry);
mActiveNotifications.add(mBubbleEntry);
when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications);
when(mNotificationEntry.getRow()).thenReturn(mNotificationRow);
mNotificationTestHelper = new NotificationTestHelper(mContext);
mNotificationRow = mNotificationTestHelper.createRow();
mNotificationActivityStarter = new StatusBarNotificationActivityStarter(getContext(),
mock(CommandQueue.class), mAssistManager, mock(NotificationPanelView.class),
mock(NotificationPresenter.class), mEntryManager, mock(HeadsUpManagerPhone.class),
mActivityStarter, mock(ActivityLaunchAnimator.class), mStatusBarService,
mock(StatusBarStateController.class), mock(KeyguardManager.class),
mock(IDreamManager.class), mRemoteInputManager,
mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardMonitor,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
mock(LockPatternUtils.class), mHandler, mActivityIntentHelper);
when(mContentIntent.isActivity()).thenReturn(true);
when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
// SBNActivityStarter expects contentIntent or fullScreenIntent to be set
mNotificationRow.getEntry().notification.getNotification().contentIntent = mContentIntent;
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute(
any(ActivityStarter.OnDismissAction.class), any(), anyBoolean());
// set up addAfterKeyguardGoneRunnable to synchronously invoke the Runnable arg
doAnswer(answerVoid(Runnable::run))
.when(mShadeController).addAfterKeyguardGoneRunnable(any(Runnable.class));
// set up addPostCollapseAction to synchronously invoke the Runnable arg
doAnswer(answerVoid(Runnable::run))
.when(mShadeController).addPostCollapseAction(any(Runnable.class));
// set up Handler to synchronously invoke the Runnable arg
doAnswer(answerVoid(Runnable::run))
.when(mHandler).post(any(Runnable.class));
}
@Test
public void testOnNotificationClicked_whileKeyguardVisible()
throws PendingIntent.CanceledException, RemoteException {
// Given
when(mKeyguardMonitor.isShowing()).thenReturn(true);
when(mShadeController.isOccluded()).thenReturn(true);
when(mContentIntent.isActivity()).thenReturn(true);
when(mActivityIntentHelper.wouldShowOverLockscreen(any(Intent.class), anyInt()))
.thenReturn(false);
when(mActivityIntentHelper.wouldLaunchResolverActivity(any(Intent.class), anyInt()))
.thenReturn(false);
StatusBarNotification statusBarNotification = mNotificationRow.getEntry().notification;
statusBarNotification.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
// When
mNotificationActivityStarter.onNotificationClicked(statusBarNotification,
mNotificationRow);
// Then
verify(mActivityStarter).dismissKeyguardThenExecute(
any(ActivityStarter.OnDismissAction.class),
any() /* cancel */,
anyBoolean() /* afterKeyguardGone */);
verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mContentIntent).sendAndReturnResult(
any(Context.class),
anyInt() /* code */,
any() /* fillInIntent */,
any() /* PendingIntent.OnFinished */,
any() /* Handler */,
any() /* requiredPermission */,
any() /* Bundle options */);
verify(mAssistManager).hideAssist();
verify(mStatusBarService).onNotificationClick(
eq(mNotificationRow.getEntry().key), any(NotificationVisibility.class));
// Notification is removed due to FLAG_AUTO_CANCEL
verify(mEntryManager).performRemoveNotification(eq(statusBarNotification));
}
}