Merge "Only show smart actions for whitelisted apps in lock task mode."

This commit is contained in:
TreeHugger Robot
2019-03-04 11:38:18 +00:00
committed by Android (Google) Code Review
7 changed files with 262 additions and 17 deletions

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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) {