summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-06-11 16:37:14 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-06-11 16:37:14 +0000
commit5e464b76228de791b3ad3b0b8cd2d44f13169089 (patch)
treec5218bf3740dfeb95fbeba192260691f8cc70ff6
parent8be6a2dc5e4aaf7208b3b3afc8d1ccf8c7956ade (diff)
parent25e9539fad62287b05fba53bbb90c1050ef6f840 (diff)
downloadbase-android-security-10.0.0_r69.tar.gz
Merge cherrypicks of [16564590, 17823648, 18670434, 18715266, 18701369] into security-aosp-qt-release.android-security-10.0.0_r69
Change-Id: Iddd72295c0f836acaf477f3ac187cc0c576362fd
-rw-r--r--core/java/android/content/AbstractThreadedSyncAdapter.java20
-rw-r--r--core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java10
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java13
-rw-r--r--services/core/java/com/android/server/connectivity/PacManager.java19
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java109
-rw-r--r--services/core/java/com/android/server/wm/TaskRecord.java51
-rw-r--r--services/tests/uiservicestests/AndroidManifest.xml1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java135
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java64
9 files changed, 387 insertions, 35 deletions
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index a086a308d0d9..da4ecdd8c1f2 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -21,6 +21,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.accounts.Account;
import android.annotation.MainThread;
import android.annotation.NonNull;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -171,8 +172,20 @@ public abstract class AbstractThreadedSyncAdapter {
}
private class ISyncAdapterImpl extends ISyncAdapter.Stub {
+ private boolean isCallerSystem() {
+ final long callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ android.util.EventLog.writeEvent(0x534e4554, "203229608", -1, "");
+ return false;
+ }
+ return true;
+ }
+
@Override
public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) {
+ if (!isCallerSystem()) {
+ return;
+ }
Handler.getMain().sendMessage(obtainMessage(
AbstractThreadedSyncAdapter::handleOnUnsyncableAccount,
AbstractThreadedSyncAdapter.this, cb));
@@ -181,12 +194,16 @@ public abstract class AbstractThreadedSyncAdapter {
@Override
public void startSync(ISyncContext syncContext, String authority, Account account,
Bundle extras) {
+ if (!isCallerSystem()) {
+ return;
+ }
if (ENABLE_LOG) {
if (extras != null) {
extras.size(); // Unparcel so its toString() will show the contents.
}
Log.d(TAG, "startSync() start " + authority + " " + account + " " + extras);
}
+
try {
final SyncContext syncContextClient = new SyncContext(syncContext);
@@ -242,6 +259,9 @@ public abstract class AbstractThreadedSyncAdapter {
@Override
public void cancelSync(ISyncContext syncContext) {
+ if (!isCallerSystem()) {
+ return;
+ }
try {
// synchronize to make sure that mSyncThreads doesn't change between when we
// check it and when we use it
diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
index 4ce6f609ef73..fdf0e9046eef 100644
--- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
+++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
@@ -17,6 +17,7 @@
package com.android.internal.notification;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
public final class NotificationAccessConfirmationActivityContract {
@@ -25,13 +26,14 @@ public final class NotificationAccessConfirmationActivityContract {
"com.android.settings.notification.NotificationAccessConfirmationActivity");
public static final String EXTRA_USER_ID = "user_id";
public static final String EXTRA_COMPONENT_NAME = "component_name";
- public static final String EXTRA_PACKAGE_TITLE = "package_title";
- public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) {
+ /**
+ * Creates a launcher intent for NotificationAccessConfirmationActivity.
+ */
+ public static Intent launcherIntent(Context context, int userId, ComponentName component) {
return new Intent()
.setComponent(COMPONENT_NAME)
.putExtra(EXTRA_USER_ID, userId)
- .putExtra(EXTRA_COMPONENT_NAME, component)
- .putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
+ .putExtra(EXTRA_COMPONENT_NAME, component);
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index d7d457395798..c11a9b058562 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -39,7 +39,6 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -310,20 +309,12 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
String callingPackage = component.getPackageName();
checkCanCallNotificationApi(callingPackage);
int userId = getCallingUserId();
- String packageTitle = BidiFormatter.getInstance().unicodeWrap(
- getPackageInfo(callingPackage, userId)
- .applicationInfo
- .loadSafeLabel(getContext().getPackageManager(),
- PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
- PackageItemInfo.SAFE_LABEL_FLAG_TRIM
- | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE)
- .toString());
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivity(getContext(),
0 /* request code */,
NotificationAccessConfirmationActivityContract.launcherIntent(
- userId, component, packageTitle),
+ getContext(), userId, component),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT);
} finally {
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index f6ce2dc68b99..621c9c71df58 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -37,6 +37,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
+import android.webkit.URLUtil;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.TrafficStatsConstants;
@@ -215,8 +216,22 @@ public class PacManager {
* @throws IOException if the URL is malformed, or the PAC file is too big.
*/
private static String get(Uri pacUri) throws IOException {
- URL url = new URL(pacUri.toString());
- URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
+ if (!URLUtil.isValidUrl(pacUri.toString())) {
+ throw new IOException("Malformed URL:" + pacUri);
+ }
+
+ final URL url = new URL(pacUri.toString());
+ URLConnection urlConnection;
+ try {
+ urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
+ // Catch the possible exceptions and rethrow as IOException to not to crash the system
+ // for illegal input.
+ } catch (IllegalArgumentException e) {
+ throw new IOException("Incorrect proxy type for " + pacUri);
+ } catch (UnsupportedOperationException e) {
+ throw new IOException("Unsupported URL connection type for " + pacUri);
+ }
+
long contentLength = -1;
try {
contentLength = Long.parseLong(urlConnection.getHeaderField("Content-Length"));
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f370335e6a79..86ca7dc87ef6 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -203,6 +203,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.StatsLog;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -228,6 +229,7 @@ import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.DeviceIdleController;
import com.android.server.EventLogTags;
import com.android.server.IoThread;
@@ -1455,6 +1457,54 @@ public class NotificationManagerService extends SystemService {
return out;
}
+ protected class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+ SparseBooleanArray mUserInLockDownMode = new SparseBooleanArray();
+ boolean mIsInLockDownMode = false;
+
+ StrongAuthTracker(Context context) {
+ super(context);
+ }
+
+ private boolean containsFlag(int haystack, int needle) {
+ return (haystack & needle) != 0;
+ }
+
+ public boolean isInLockDownMode() {
+ return mIsInLockDownMode;
+ }
+
+ @Override
+ public synchronized void onStrongAuthRequiredChanged(int userId) {
+ boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId),
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ mUserInLockDownMode.put(userId, userInLockDownModeNext);
+ boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1;
+
+ if (mIsInLockDownMode == isInLockDownModeNext) {
+ return;
+ }
+
+ if (isInLockDownModeNext) {
+ cancelNotificationsWhenEnterLockDownMode();
+ }
+
+ // When the mIsInLockDownMode is true, both notifyPostedLocked and
+ // notifyRemovedLocked will be dismissed. So we shall call
+ // cancelNotificationsWhenEnterLockDownMode before we set mIsInLockDownMode
+ // as true and call postNotificationsWhenExitLockDownMode after we set
+ // mIsInLockDownMode as false.
+ mIsInLockDownMode = isInLockDownModeNext;
+
+ if (!isInLockDownModeNext) {
+ postNotificationsWhenExitLockDownMode();
+ }
+ }
+ }
+
+ private LockPatternUtils mLockPatternUtils;
+ private StrongAuthTracker mStrongAuthTracker;
+
public NotificationManagerService(Context context) {
super(context);
Notification.processWhitelistToken = WHITELIST_TOKEN;
@@ -1467,6 +1517,11 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
+ void setStrongAuthTracker(StrongAuthTracker strongAuthTracker) {
+ mStrongAuthTracker = strongAuthTracker;
+ }
+
+ @VisibleForTesting
void setKeyguardManager(KeyguardManager keyguardManager) {
mKeyguardManager = keyguardManager;
}
@@ -1641,6 +1696,8 @@ public class NotificationManagerService extends SystemService {
mHandler = new WorkerHandler(looper);
mRankingThread.start();
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ mStrongAuthTracker = new StrongAuthTracker(getContext());
String[] extractorNames;
try {
extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -1783,7 +1840,8 @@ public class NotificationManagerService extends SystemService {
init(Looper.myLooper(),
AppGlobals.getPackageManager(), getContext().getPackageManager(),
getLocalService(LightsManager.class),
- new NotificationListeners(AppGlobals.getPackageManager()),
+ new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,
+ AppGlobals.getPackageManager()),
new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
AppGlobals.getPackageManager()),
new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
@@ -1926,6 +1984,7 @@ public class NotificationManagerService extends SystemService {
mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
mPackageManager, getContext().getMainExecutor());
mRoleObserver.init();
+ mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
// bind to listener services.
@@ -7169,6 +7228,29 @@ public class NotificationManagerService extends SystemService {
}
}
+ private void cancelNotificationsWhenEnterLockDownMode() {
+ synchronized (mNotificationLock) {
+ int numNotifications = mNotificationList.size();
+ for (int i = 0; i < numNotifications; i++) {
+ NotificationRecord rec = mNotificationList.get(i);
+ mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL,
+ rec.getStats());
+ }
+
+ }
+ }
+
+ private void postNotificationsWhenExitLockDownMode() {
+ synchronized (mNotificationLock) {
+ int numNotifications = mNotificationList.size();
+ for (int i = 0; i < numNotifications; i++) {
+ NotificationRecord rec = mNotificationList.get(i);
+ mListeners.notifyPostedLocked(rec, rec);
+ }
+
+ }
+ }
+
private void updateNotificationPulse() {
synchronized (mNotificationLock) {
updateLightsLocked();
@@ -7378,6 +7460,10 @@ public class NotificationManagerService extends SystemService {
rankings.toArray(new NotificationListenerService.Ranking[0]));
}
+ boolean isInLockDownMode() {
+ return mStrongAuthTracker.isInLockDownMode();
+ }
+
boolean hasCompanionDevice(ManagedServiceInfo info) {
if (mCompanionManager == null) {
mCompanionManager = getCompanionManager();
@@ -7885,9 +7971,9 @@ public class NotificationManagerService extends SystemService {
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
- public NotificationListeners(IPackageManager pm) {
- super(getContext(), mNotificationLock, mUserProfiles, pm);
-
+ public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
+ IPackageManager pm) {
+ super(context, lock, userProfiles, pm);
}
@Override
@@ -7996,8 +8082,12 @@ public class NotificationManagerService extends SystemService {
* targetting <= O_MR1
*/
@GuardedBy("mNotificationLock")
- private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
+ void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
boolean notifyAllListeners) {
+ if (isInLockDownMode()) {
+ return;
+ }
+
// Lazily initialized snapshots of the notification.
StatusBarNotification sbn = r.sbn;
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
@@ -8060,8 +8150,11 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
public void notifyRemovedLocked(NotificationRecord r, int reason,
NotificationStats notificationStats) {
- final StatusBarNotification sbn = r.sbn;
+ if (isInLockDownMode()) {
+ return;
+ }
+ final StatusBarNotification sbn = r.sbn;
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
// notification
@@ -8112,6 +8205,10 @@ public class NotificationManagerService extends SystemService {
*/
@GuardedBy("mNotificationLock")
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
+ if (isInLockDownMode()) {
+ return;
+ }
+
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 3fd4e83b9494..361f66e3106a 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -110,6 +110,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -204,6 +205,11 @@ class TaskRecord extends ConfigurationContainer {
static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
/**
+ * Used to identify if the activity that is installed from device's system image.
+ */
+ boolean mIsEffectivelySystemApp;
+
+ /**
* The factory used to create {@link TaskRecord}. This allows OEM subclass {@link TaskRecord}.
*/
private static TaskRecordFactory sTaskRecordFactory;
@@ -869,17 +875,25 @@ class TaskRecord extends ConfigurationContainer {
/** Sets the original intent, and the calling uid and package. */
void setIntent(ActivityRecord r) {
- mCallingUid = r.launchedFromUid;
- mCallingPackage = r.launchedFromPackage;
- setIntent(r.intent, r.info);
+ boolean updateIdentity = false;
+ if (this.intent == null) {
+ updateIdentity = true;
+ } else if (!mNeverRelinquishIdentity) {
+ updateIdentity = (effectiveUid == Process.SYSTEM_UID || mIsEffectivelySystemApp
+ || effectiveUid == r.info.applicationInfo.uid);
+ }
+ if (updateIdentity) {
+ mCallingUid = r.launchedFromUid;
+ mCallingPackage = r.launchedFromPackage;
+ setIntent(r.intent, r.info);
+ }
setLockTaskAuth(r);
}
/** Sets the original intent, _without_ updating the calling uid or package. */
private void setIntent(Intent _intent, ActivityInfo info) {
if (intent == null) {
- mNeverRelinquishIdentity =
- (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
} else if (mNeverRelinquishIdentity) {
return;
}
@@ -892,6 +906,7 @@ class TaskRecord extends ConfigurationContainer {
rootAffinity = affinity;
}
effectiveUid = info.applicationInfo.uid;
+ mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
stringName = null;
if (info.targetActivity == null) {
@@ -1662,12 +1677,12 @@ class TaskRecord extends ConfigurationContainer {
// utility activities.
int activityNdx;
final int numActivities = mActivities.size();
- final boolean relinquish = numActivities != 0 &&
- (mActivities.get(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
- for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
- ++activityNdx) {
+ for (activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
- if (relinquish && (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+ if ((r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0
+ || (r.info.applicationInfo.uid != Process.SYSTEM_UID
+ && !r.info.applicationInfo.isSystemApp()
+ && r.info.applicationInfo.uid != effectiveUid)) {
// This will be the top activity for determining taskDescription. Pre-inc to
// overcome initial decrement below.
++activityNdx;
@@ -1739,15 +1754,27 @@ class TaskRecord extends ConfigurationContainer {
int findEffectiveRootIndex() {
int effectiveNdx = 0;
final int topActivityNdx = mActivities.size() - 1;
+ ActivityRecord root = null;
for (int activityNdx = 0; activityNdx <= topActivityNdx; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
if (r.finishing) {
continue;
}
- effectiveNdx = activityNdx;
- if ((r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+
+ if (root == null) {
+ // Set this as the candidate root since it isn't finishing.
+ root = r;
+ effectiveNdx = activityNdx;
+ }
+ final int uid = root == r ? effectiveUid : r.info.applicationInfo.uid;
+ if ((root.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0
+ || (root.info.applicationInfo.uid != Process.SYSTEM_UID
+ && !root.info.applicationInfo.isSystemApp()
+ && root.info.applicationInfo.uid != uid)) {
break;
}
+ effectiveNdx = activityNdx;
+ root = r;
}
return effectiveNdx;
}
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index 7453c489ecc8..5b926e041b42 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -31,6 +31,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
+ <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
new file mode 100644
index 000000000000..793739bfe8f5
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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.notification;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.testing.TestableContext;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.util.List;
+
+public class NotificationListenersTest extends UiServiceTestCase {
+
+ @Mock
+ private PackageManager mPm;
+ @Mock
+ private IPackageManager miPm;
+
+ @Mock
+ NotificationManagerService mNm;
+ @Mock
+ private INotificationManager mINm;
+ private TestableContext mContext = spy(getContext());
+
+ NotificationManagerService.NotificationListeners mListeners;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ getContext().setMockPackageManager(mPm);
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+
+ mListeners = spy(mNm.new NotificationListeners(
+ mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm));
+ when(mNm.getBinderService()).thenReturn(mINm);
+ }
+
+ @Test
+ public void testNotifyPostedLockedInLockdownMode() {
+ NotificationRecord r = mock(NotificationRecord.class);
+ NotificationRecord old = mock(NotificationRecord.class);
+
+ // before the lockdown mode
+ when(mNm.isInLockDownMode()).thenReturn(false);
+ mListeners.notifyPostedLocked(r, old, true);
+ mListeners.notifyPostedLocked(r, old, false);
+ verify(mListeners, times(2)).getServices();
+
+ // in the lockdown mode
+ reset(r);
+ reset(old);
+ reset(mListeners);
+ when(mNm.isInLockDownMode()).thenReturn(true);
+ mListeners.notifyPostedLocked(r, old, true);
+ mListeners.notifyPostedLocked(r, old, false);
+ verify(mListeners, never()).getServices();
+ }
+
+ @Test
+ public void testnotifyRankingUpdateLockedInLockdownMode() {
+ List chn = mock(List.class);
+
+ // before the lockdown mode
+ when(mNm.isInLockDownMode()).thenReturn(false);
+ mListeners.notifyRankingUpdateLocked(chn);
+ verify(chn, times(1)).size();
+
+ // in the lockdown mode
+ reset(chn);
+ when(mNm.isInLockDownMode()).thenReturn(true);
+ mListeners.notifyRankingUpdateLocked(chn);
+ verify(chn, never()).size();
+ }
+
+ @Test
+ public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException {
+ StatusBarNotification sbn = mock(StatusBarNotification.class);
+ NotificationRecord r = mock(NotificationRecord.class);
+ NotificationStats rs = mock(NotificationStats.class);
+ FieldSetter.setField(r,
+ NotificationRecord.class.getDeclaredField("sbn"),
+ sbn);
+ FieldSetter.setField(mNm,
+ NotificationManagerService.class.getDeclaredField("mHandler"),
+ mock(NotificationManagerService.WorkerHandler.class));
+
+ // before the lockdown mode
+ when(mNm.isInLockDownMode()).thenReturn(false);
+ mListeners.notifyRemovedLocked(r, 0, rs);
+ mListeners.notifyRemovedLocked(r, 0, rs);
+ verify(sbn, times(2)).cloneLight();
+
+ // in the lockdown mode
+ reset(sbn);
+ reset(r);
+ reset(rs);
+ when(mNm.isInLockDownMode()).thenReturn(true);
+ mListeners.notifyRemovedLocked(r, 0, rs);
+ mListeners.notifyRemovedLocked(r, 0, rs);
+ verify(sbn, never()).cloneLight();
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 997cc266f550..30f5fae52993 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -45,9 +45,12 @@ import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -321,8 +324,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Tests for this not being true are in CTS NotificationManagerTest
return true;
}
+
+ class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
+ private int mGetStrongAuthForUserReturnValue = 0;
+ StrongAuthTrackerFake(Context context) {
+ super(context);
+ }
+
+ public void setGetStrongAuthForUserReturnValue(int val) {
+ mGetStrongAuthForUserReturnValue = val;
+ }
+
+ @Override
+ public int getStrongAuthForUser(int userId) {
+ return mGetStrongAuthForUserReturnValue;
+ }
+ }
}
+ TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
+
private class TestableToastCallback extends ITransientNotification.Stub {
@Override
public void show(IBinder windowToken) {
@@ -411,6 +432,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.setAudioManager(mAudioManager);
+ mStrongAuthTracker = mService.new StrongAuthTrackerFake(mContext);
+ mService.setStrongAuthTracker(mStrongAuthTracker);
+
// Tests call directly into the Binder.
mBinderService = mService.getBinderService();
mInternalService = mService.getInternalService();
@@ -5219,4 +5243,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
}
}
+
+ @Test
+ public void testStrongAuthTracker_isInLockDownMode() {
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
+ assertTrue(mStrongAuthTracker.isInLockDownMode());
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
+ mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
+ assertFalse(mStrongAuthTracker.isInLockDownMode());
+ }
+
+ @Test
+ public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() {
+ // post 2 notifications from 2 packages
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgB);
+
+ // when entering the lockdown mode, cancel the 2 notifications.
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
+ assertTrue(mStrongAuthTracker.isInLockDownMode());
+
+ // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN.
+ ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
+ verify(mListeners, times(2)).notifyRemovedLocked(any(), captor.capture(), any());
+ assertEquals(REASON_CANCEL_ALL, captor.getValue().intValue());
+
+ // exit lockdown mode.
+ mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
+ mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
+
+ // the notifyPostedLocked function is called twice.
+ verify(mListeners, times(2)).notifyPostedLocked(any(), any());
+ }
}