Merge "Only show smart actions for whitelisted apps in lock task mode."
This commit is contained in:
committed by
Android (Google) Code Review
commit
5766681ae5
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.shared.system;
|
||||
|
||||
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
|
||||
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
|
||||
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
|
||||
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
|
||||
@@ -463,6 +464,17 @@ public class ActivityManagerWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether lock task mode is active in kiosk-mode (not screen pinning).
|
||||
*/
|
||||
public boolean isLockTaskKioskModeActive() {
|
||||
try {
|
||||
return ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_LOCKED;
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a voice session identified by {@code token}
|
||||
* @return true if the session was shown, false otherwise
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.shared.system;
|
||||
|
||||
import android.app.AppGlobals;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
|
||||
/**
|
||||
* Wrapper for {@link DevicePolicyManager}.
|
||||
*/
|
||||
public class DevicePolicyManagerWrapper {
|
||||
private static final DevicePolicyManagerWrapper sInstance = new DevicePolicyManagerWrapper();
|
||||
|
||||
private static final DevicePolicyManager sDevicePolicyManager =
|
||||
AppGlobals.getInitialApplication().getSystemService(DevicePolicyManager.class);
|
||||
|
||||
private DevicePolicyManagerWrapper() { }
|
||||
|
||||
public static DevicePolicyManagerWrapper getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given package is allowed to run in Lock Task mode.
|
||||
*/
|
||||
public boolean isLockTaskPermitted(String pkg) {
|
||||
return sDevicePolicyManager.isLockTaskPermitted(pkg);
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,10 @@ import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.ResolveInfoFlags;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -40,6 +42,8 @@ public class PackageManagerWrapper {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private PackageManagerWrapper() {}
|
||||
|
||||
/**
|
||||
* @return the activity info for a given {@param componentName} and {@param userId}.
|
||||
*/
|
||||
@@ -65,4 +69,19 @@ public class PackageManagerWrapper {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the best Activity to perform for a given Intent.
|
||||
*/
|
||||
public ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags) {
|
||||
final String resolvedType =
|
||||
intent.resolveTypeIfNeeded(AppGlobals.getInitialApplication().getContentResolver());
|
||||
try {
|
||||
return mIPackageManager.resolveIntent(
|
||||
intent, resolvedType, flags, UserHandle.getCallingUserId());
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ import com.android.systemui.power.PowerUI;
|
||||
import com.android.systemui.privacy.PrivacyItemController;
|
||||
import com.android.systemui.recents.OverviewProxyService;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
import com.android.systemui.statusbar.AmbientPulseManager;
|
||||
import com.android.systemui.statusbar.NavigationBarController;
|
||||
import com.android.systemui.statusbar.NotificationListener;
|
||||
@@ -285,6 +288,9 @@ public class Dependency extends SystemUI {
|
||||
@Nullable
|
||||
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
|
||||
@Inject Lazy<ClockManager> mClockManager;
|
||||
@Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
|
||||
@Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
|
||||
@Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
|
||||
|
||||
@Inject
|
||||
public Dependency() {
|
||||
@@ -452,6 +458,9 @@ public class Dependency extends SystemUI {
|
||||
mForegroundServiceNotificationListener::get);
|
||||
mProviders.put(ClockManager.class, mClockManager::get);
|
||||
mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
|
||||
mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
|
||||
mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
|
||||
mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
|
||||
|
||||
|
||||
// TODO(b/118592525): to support multi-display , we start to add something which is
|
||||
|
||||
@@ -41,6 +41,9 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.systemui.plugins.PluginInitializerImpl;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.shared.plugins.PluginManagerImpl;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
import com.android.systemui.statusbar.NavigationBarController;
|
||||
import com.android.systemui.statusbar.phone.AutoHideController;
|
||||
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
|
||||
@@ -181,4 +184,22 @@ public class DependencyProvider {
|
||||
@Named(MAIN_HANDLER_NAME) Handler mainHandler) {
|
||||
return new AutoHideController(context, mainHandler);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
public ActivityManagerWrapper provideActivityManagerWrapper() {
|
||||
return ActivityManagerWrapper.getInstance();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() {
|
||||
return DevicePolicyManagerWrapper.getInstance();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
public PackageManagerWrapper providePackageManagerWrapper() {
|
||||
return PackageManagerWrapper.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,18 @@ import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
import com.android.systemui.statusbar.SmartReplyController;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
|
||||
@@ -191,13 +197,46 @@ public class InflatedSmartReplies {
|
||||
boolean useSmartActions = !ArrayUtils.isEmpty(entry.systemGeneratedSmartActions)
|
||||
&& notification.getAllowSystemGeneratedContextualActions();
|
||||
if (useSmartActions) {
|
||||
List<Notification.Action> systemGeneratedActions =
|
||||
entry.systemGeneratedSmartActions;
|
||||
// Filter actions if we're in kiosk-mode - we don't care about screen pinning mode,
|
||||
// since notifications aren't shown there anyway.
|
||||
ActivityManagerWrapper activityManagerWrapper =
|
||||
Dependency.get(ActivityManagerWrapper.class);
|
||||
if (activityManagerWrapper.isLockTaskKioskModeActive()) {
|
||||
systemGeneratedActions = filterWhiteListedLockTaskApps(systemGeneratedActions);
|
||||
}
|
||||
smartActions = new SmartReplyView.SmartActions(
|
||||
entry.systemGeneratedSmartActions, true /* fromAssistant */);
|
||||
systemGeneratedActions, true /* fromAssistant */);
|
||||
}
|
||||
}
|
||||
return new SmartRepliesAndActions(smartReplies, smartActions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter actions so that only actions pointing to whitelisted apps are allowed.
|
||||
* This filtering is only meaningful when in lock-task mode.
|
||||
*/
|
||||
private static List<Notification.Action> filterWhiteListedLockTaskApps(
|
||||
List<Notification.Action> actions) {
|
||||
PackageManagerWrapper packageManagerWrapper = Dependency.get(PackageManagerWrapper.class);
|
||||
DevicePolicyManagerWrapper devicePolicyManagerWrapper =
|
||||
Dependency.get(DevicePolicyManagerWrapper.class);
|
||||
List<Notification.Action> filteredActions = new ArrayList<>();
|
||||
for (Notification.Action action : actions) {
|
||||
if (action.actionIntent == null) continue;
|
||||
Intent intent = action.actionIntent.getIntent();
|
||||
// Only allow actions that are explicit (implicit intents are not handled in lock-task
|
||||
// mode), and link to whitelisted apps.
|
||||
ResolveInfo resolveInfo = packageManagerWrapper.resolveActivity(intent, 0 /* flags */);
|
||||
if (resolveInfo != null && devicePolicyManagerWrapper.isLockTaskPermitted(
|
||||
resolveInfo.activityInfo.packageName)) {
|
||||
filteredActions.add(action);
|
||||
}
|
||||
}
|
||||
return filteredActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Notification} represented by entry has a free-form remote input.
|
||||
* Such an input can be used e.g. to implement smart reply buttons - by passing the replies
|
||||
|
||||
@@ -11,19 +11,23 @@
|
||||
* 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.
|
||||
*/
|
||||
* limitations under the License. */
|
||||
|
||||
package com.android.systemui.statusbar.policy;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Pair;
|
||||
@@ -34,6 +38,9 @@ import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
|
||||
|
||||
@@ -50,19 +57,19 @@ import java.util.List;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class InflatedSmartRepliesTest extends SysuiTestCase {
|
||||
|
||||
private static final String TEST_ACTION = "com.android.SMART_REPLY_VIEW_ACTION";
|
||||
private static final Intent TEST_INTENT = new Intent("com.android.SMART_REPLY_VIEW_ACTION");
|
||||
private static final Intent WHITELISTED_TEST_INTENT =
|
||||
new Intent("com.android.WHITELISTED_TEST_ACTION");
|
||||
|
||||
@Mock
|
||||
SmartReplyConstants mSmartReplyConstants;
|
||||
@Mock
|
||||
StatusBarNotification mStatusBarNotification;
|
||||
@Mock
|
||||
Notification mNotification;
|
||||
@Mock SmartReplyConstants mSmartReplyConstants;
|
||||
@Mock StatusBarNotification mStatusBarNotification;
|
||||
@Mock Notification mNotification;
|
||||
NotificationEntry mEntry;
|
||||
@Mock
|
||||
RemoteInput mRemoteInput;
|
||||
@Mock
|
||||
RemoteInput mFreeFormRemoteInput;
|
||||
@Mock RemoteInput mRemoteInput;
|
||||
@Mock RemoteInput mFreeFormRemoteInput;
|
||||
@Mock ActivityManagerWrapper mActivityManagerWrapper;
|
||||
@Mock PackageManagerWrapper mPackageManagerWrapper;
|
||||
@Mock DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
|
||||
|
||||
private Icon mActionIcon;
|
||||
|
||||
@@ -71,11 +78,18 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mDependency.injectTestDependency(ActivityManagerWrapper.class, mActivityManagerWrapper);
|
||||
mDependency.injectTestDependency(
|
||||
DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper);
|
||||
mDependency.injectTestDependency(PackageManagerWrapper.class, mPackageManagerWrapper);
|
||||
|
||||
when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true);
|
||||
when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
|
||||
mEntry = new NotificationEntry(mStatusBarNotification);
|
||||
when(mSmartReplyConstants.isEnabled()).thenReturn(true);
|
||||
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
|
||||
|
||||
when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,6 +241,87 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
|
||||
assertThat(repliesAndActions.smartReplies).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartRepliesUnaffected() {
|
||||
when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(true);
|
||||
// No apps are white-listed
|
||||
when(mDevicePolicyManagerWrapper.isLockTaskPermitted(anyString())).thenReturn(false);
|
||||
|
||||
// Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
|
||||
// suggestions.
|
||||
setupAppGeneratedReplies(null /* smartReplies */);
|
||||
mEntry.systemGeneratedSmartReplies =
|
||||
new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
|
||||
mEntry.systemGeneratedSmartActions =
|
||||
createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
|
||||
|
||||
SmartRepliesAndActions repliesAndActions =
|
||||
InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
|
||||
|
||||
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
|
||||
mEntry.systemGeneratedSmartReplies);
|
||||
// Since no apps are whitelisted no actions should be shown.
|
||||
assertThat(repliesAndActions.smartActions.actions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartActionsAffected() {
|
||||
when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(true);
|
||||
String allowedPackage = "allowedPackage";
|
||||
ResolveInfo allowedResolveInfo = new ResolveInfo();
|
||||
allowedResolveInfo.activityInfo = new ActivityInfo();
|
||||
allowedResolveInfo.activityInfo.packageName = allowedPackage;
|
||||
when(mPackageManagerWrapper
|
||||
.resolveActivity(
|
||||
argThat(intent -> WHITELISTED_TEST_INTENT.getAction().equals(
|
||||
intent.getAction())),
|
||||
anyInt() /* flags */))
|
||||
.thenReturn(allowedResolveInfo);
|
||||
when(mDevicePolicyManagerWrapper.isLockTaskPermitted(allowedPackage)).thenReturn(true);
|
||||
|
||||
// Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
|
||||
// suggestions.
|
||||
setupAppGeneratedReplies(null /* smartReplies */);
|
||||
mEntry.systemGeneratedSmartReplies =
|
||||
new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
|
||||
List<Notification.Action> actions = new ArrayList<>();
|
||||
actions.add(createAction("allowed action", WHITELISTED_TEST_INTENT));
|
||||
actions.add(createAction("non-allowed action", TEST_INTENT));
|
||||
mEntry.systemGeneratedSmartActions = actions;
|
||||
|
||||
SmartRepliesAndActions repliesAndActions =
|
||||
InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
|
||||
|
||||
// Only the action for the whitelisted package should be allowed.
|
||||
assertThat(repliesAndActions.smartActions.actions.size()).isEqualTo(1);
|
||||
assertThat(repliesAndActions.smartActions.actions.get(0)).isEqualTo(
|
||||
mEntry.systemGeneratedSmartActions.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chooseSmartRepliesAndActions_screenPinningModeEnabled_suggestionsUnaffected() {
|
||||
when(mActivityManagerWrapper.isLockToAppActive()).thenReturn(true);
|
||||
// No apps are white-listed
|
||||
when(mDevicePolicyManagerWrapper.isLockTaskPermitted(anyString())).thenReturn(false);
|
||||
|
||||
// Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
|
||||
// suggestions.
|
||||
setupAppGeneratedReplies(null /* smartReplies */);
|
||||
mEntry.systemGeneratedSmartReplies =
|
||||
new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
|
||||
mEntry.systemGeneratedSmartActions =
|
||||
createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
|
||||
|
||||
SmartRepliesAndActions repliesAndActions =
|
||||
InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
|
||||
|
||||
// We don't restrict replies or actions in screen pinning mode.
|
||||
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
|
||||
mEntry.systemGeneratedSmartReplies);
|
||||
assertThat(repliesAndActions.smartActions.actions).isEqualTo(
|
||||
mEntry.systemGeneratedSmartActions);
|
||||
}
|
||||
|
||||
private void setupAppGeneratedReplies(CharSequence[] smartReplies) {
|
||||
setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */);
|
||||
}
|
||||
@@ -234,7 +329,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
|
||||
private void setupAppGeneratedReplies(
|
||||
CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) {
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0);
|
||||
PendingIntent.getBroadcast(mContext, 0, TEST_INTENT, 0);
|
||||
Notification.Action action =
|
||||
new Notification.Action.Builder(null, "Test Action", pendingIntent).build();
|
||||
when(mRemoteInput.getChoices()).thenReturn(smartReplies);
|
||||
@@ -261,8 +356,11 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
private Notification.Action.Builder createActionBuilder(String actionTitle) {
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
|
||||
new Intent(TEST_ACTION), 0);
|
||||
return createActionBuilder(actionTitle, TEST_INTENT);
|
||||
}
|
||||
|
||||
private Notification.Action.Builder createActionBuilder(String actionTitle, Intent intent) {
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
|
||||
return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent);
|
||||
}
|
||||
|
||||
@@ -270,6 +368,10 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
|
||||
return createActionBuilder(actionTitle).build();
|
||||
}
|
||||
|
||||
private Notification.Action createAction(String actionTitle, Intent intent) {
|
||||
return createActionBuilder(actionTitle, intent).build();
|
||||
}
|
||||
|
||||
private List<Notification.Action> createActions(String[] actionTitles) {
|
||||
List<Notification.Action> actions = new ArrayList<>();
|
||||
for (String title : actionTitles) {
|
||||
|
||||
Reference in New Issue
Block a user