Merge changes from topic "presubmit-am-cbc3dc3a1fb64fea92fed20dc6a3f23e" into tm-mainline-prod

* changes:
  [automerge] Allow registered call notifications to always appear 2p: 20a47d5ed0
  Allow registered call notifications to always appear
This commit is contained in:
Julia Reynolds
2022-04-13 13:06:45 +00:00
committed by Android (Google) Code Review
11 changed files with 224 additions and 10 deletions

View File

@@ -57,6 +57,7 @@ interface INotificationManager
@UnsupportedAppUsage
void cancelNotificationWithTag(String pkg, String opPkg, String tag, int id, int userId);
boolean isInCall(String pkg, int uid);
void setShowBadge(String pkg, int uid, boolean showBadge);
boolean canShowBadge(String pkg, int uid);
boolean hasSentValidMsg(String pkg, int uid);

View File

@@ -224,6 +224,7 @@
<uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />

View File

@@ -135,6 +135,15 @@ asked for it -->
android:layout_height="wrap_content"
style="@*android:style/TextAppearance.DeviceDefault.Notification" />
<!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings-->
<TextView
android:id="@+id/non_configurable_call_text"
android:text="@string/notification_unblockable_call_desc"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@*android:style/TextAppearance.DeviceDefault.Notification" />
<!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings-->
<TextView
android:id="@+id/non_configurable_multichannel_text"

View File

@@ -1373,6 +1373,9 @@
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
<!-- Notification: Control panel: Label that displays when a notification cannot be blocked because it's attached to a phone/voip call. -->
<string name="notification_unblockable_call_desc">Call notifications can\'t be modified.</string>
<!-- Notification: Control panel: label that displays when viewing settings for a group of notifications posted to multiple channels. -->
<string name="notification_multichannel_desc">This group of notifications cannot be configured here</string>

View File

@@ -32,6 +32,7 @@ import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -380,12 +381,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1) {
INotificationManager iNm = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
boolean isSystem = false;
try {
return iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId());
isSystem = iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId());
} catch (RemoteException e) {
Log.e(TAG, "cannot reach NMS");
}
return false;
RoleManager rm = context.getSystemService(RoleManager.class);
List<String> fixedRoleHolders = new ArrayList<>();
fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_DIALER));
fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_EMERGENCY));
if (fixedRoleHolders.contains(sbn.getPackageName())) {
isSystem = true;
}
return isSystem;
} else {
PackageManager packageManager = CentralSurfaces.getPackageManagerForUser(
context, sbn.getUser().getIdentifier());

View File

@@ -124,6 +124,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private boolean mIsDeviceProvisioned;
private boolean mIsSystemRegisteredCall;
private OnSettingsClickListener mOnSettingsClickListener;
private OnAppSettingsClickListener mAppSettingsClickListener;
@@ -229,6 +230,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mShowAutomaticSetting = mAssistantFeedbackController.isFeedbackEnabled();
mUiEventLogger = uiEventLogger;
mIsSystemRegisteredCall = mSbn.getNotification().isStyle(Notification.CallStyle.class)
&& mINotificationManager.isInCall(mSbn.getPackageName(), mSbn.getUid());
int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
pkg, mAppUid, false /* includeDeleted */);
if (mNumUniqueChannelsInRow == 0) {
@@ -252,17 +256,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
private void bindInlineControls() {
if (mIsNonblockable) {
if (mIsSystemRegisteredCall) {
findViewById(R.id.non_configurable_call_text).setVisibility(VISIBLE);
findViewById(R.id.non_configurable_text).setVisibility(GONE);
findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
((TextView) findViewById(R.id.done)).setText(R.string.inline_done_button);
findViewById(R.id.turn_off_notifications).setVisibility(GONE);
} else if (mIsNonblockable) {
findViewById(R.id.non_configurable_text).setVisibility(VISIBLE);
findViewById(R.id.non_configurable_call_text).setVisibility(GONE);
findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
((TextView) findViewById(R.id.done)).setText(R.string.inline_done_button);
findViewById(R.id.turn_off_notifications).setVisibility(GONE);
} else if (mNumUniqueChannelsInRow > 1) {
findViewById(R.id.non_configurable_call_text).setVisibility(GONE);
findViewById(R.id.non_configurable_text).setVisibility(GONE);
findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
findViewById(R.id.non_configurable_multichannel_text).setVisibility(VISIBLE);
} else {
findViewById(R.id.non_configurable_call_text).setVisibility(GONE);
findViewById(R.id.non_configurable_text).setVisibility(GONE);
findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);

View File

@@ -44,12 +44,15 @@ import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.PendingIntent;
import android.app.Person;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -116,11 +119,15 @@ public class NotificationInfoTest extends SysuiTestCase {
private ChannelEditorDialogController mChannelEditorDialogController;
@Mock
private AssistantFeedbackController mAssistantFeedbackController;
@Mock
private TelecomManager mTelecomManager;
@Before
public void setUp() throws Exception {
mTestableLooper = TestableLooper.get(this);
mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
// Inflate the layout
@@ -161,7 +168,7 @@ public class NotificationInfoTest extends SysuiTestCase {
IMPORTANCE_LOW);
mDefaultNotificationChannelSet.add(mDefaultNotificationChannel);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
new Notification(), UserHandle.getUserHandleForUid(TEST_UID), null, 0);
mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false);
when(mAssistantFeedbackController.getInlineDescriptionResource(any()))
@@ -631,6 +638,92 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility());
}
@Test
public void testBindNotification_whenCurrentlyInCall() throws Exception {
when(mMockINotificationManager.isInCall(anyString(), anyInt())).thenReturn(true);
Person person = new Person.Builder()
.setName("caller")
.build();
Notification.Builder nb = new Notification.Builder(
mContext, mNotificationChannel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(Notification.CallStyle.forOngoingCall(
person, mock(PendingIntent.class)))
.setFullScreenIntent(mock(PendingIntent.class), true)
.addAction(new Notification.Action.Builder(null, "test", null).build());
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
nb.build(), UserHandle.getUserHandleForUid(TEST_UID), null, 0);
mEntry.setSbn(mSbn);
mNotificationInfo.bindNotification(
mMockPackageManager,
mMockINotificationManager,
mOnUserInteractionCallback,
mChannelEditorDialogController,
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
mEntry,
null,
null,
mUiEventLogger,
true,
false,
true,
mAssistantFeedbackController);
final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_call_desc),
view.getText());
assertEquals(GONE,
mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility());
assertEquals(GONE,
mNotificationInfo.findViewById(R.id.non_configurable_text).getVisibility());
}
@Test
public void testBindNotification_whenCurrentlyInCall_notCall() throws Exception {
when(mMockINotificationManager.isInCall(anyString(), anyInt())).thenReturn(true);
Person person = new Person.Builder()
.setName("caller")
.build();
Notification.Builder nb = new Notification.Builder(
mContext, mNotificationChannel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setFullScreenIntent(mock(PendingIntent.class), true)
.addAction(new Notification.Action.Builder(null, "test", null).build());
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
nb.build(), UserHandle.getUserHandleForUid(TEST_UID), null, 0);
mEntry.setSbn(mSbn);
mNotificationInfo.bindNotification(
mMockPackageManager,
mMockINotificationManager,
mOnUserInteractionCallback,
mChannelEditorDialogController,
TEST_PACKAGE_NAME,
mNotificationChannel,
mNotificationChannelSet,
mEntry,
null,
null,
mUiEventLogger,
true,
false,
true,
mAssistantFeedbackController);
assertEquals(GONE,
mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility());
assertEquals(VISIBLE,
mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility());
assertEquals(GONE,
mNotificationInfo.findViewById(R.id.non_configurable_text).getVisibility());
}
@Test
public void testBindNotification_automaticIsVisible() throws Exception {
when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(true);

View File

@@ -240,6 +240,7 @@ import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeProto;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -509,6 +510,7 @@ public class NotificationManagerService extends SystemService {
private ShortcutHelper mShortcutHelper;
private PermissionHelper mPermissionHelper;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private TelecomManager mTelecomManager;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -2100,7 +2102,8 @@ public class NotificationManagerService extends SystemService {
NotificationHistoryManager historyManager, StatsManager statsManager,
TelephonyManager telephonyManager, ActivityManagerInternal ami,
MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
UsageStatsManagerInternal usageStatsManagerInternal) {
UsageStatsManagerInternal usageStatsManagerInternal,
TelecomManager telecomManager) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2129,6 +2132,7 @@ public class NotificationManagerService extends SystemService {
mDeviceIdleManager = getContext().getSystemService(DeviceIdleManager.class);
mDpm = dpm;
mUm = userManager;
mTelecomManager = telecomManager;
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2420,7 +2424,8 @@ public class NotificationManagerService extends SystemService {
PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
AppGlobals.getPermissionManager(), mEnableAppSettingMigration,
mForceUserSetOnUpgrade),
LocalServices.getService(UsageStatsManagerInternal.class));
LocalServices.getService(UsageStatsManagerInternal.class),
getContext().getSystemService(TelecomManager.class));
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -5534,6 +5539,12 @@ public class NotificationManagerService extends SystemService {
return new ParceledListSlice<>(groups);
}
@Override
public boolean isInCall(String pkg, int uid) {
checkCallerIsSystemOrSystemUiOrShell();
return isCallNotification(pkg, uid);
}
@Override
public void setPrivateNotificationsAllowed(boolean allow) {
if (PackageManager.PERMISSION_GRANTED
@@ -6846,7 +6857,7 @@ public class NotificationManagerService extends SystemService {
synchronized (mNotificationLock) {
isBlocked |= isRecordBlockedLocked(r);
}
if (isBlocked && !n.isMediaNotification()) {
if (isBlocked && !(n.isMediaNotification() || isCallNotification(pkg, uid, n))) {
if (DBG) {
Slog.e(TAG, "Suppressing notification from package " + r.getSbn().getPackageName()
+ " by user request.");
@@ -6858,6 +6869,23 @@ public class NotificationManagerService extends SystemService {
return true;
}
private boolean isCallNotification(String pkg, int uid, Notification n) {
if (n.isStyle(Notification.CallStyle.class)) {
return isCallNotification(pkg, uid);
}
return false;
}
private boolean isCallNotification(String pkg, int uid) {
final long identity = Binder.clearCallingIdentity();
try {
return mTelecomManager.isInManagedCall() || mTelecomManager.isInSelfManagedCall(
pkg, UserHandle.getUserHandleForUid(uid));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) {
if (mEnableAppSettingMigration) {
return mPermissionHelper.hasPermission(uid);

View File

@@ -170,6 +170,7 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -290,6 +291,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
ActivityManager mActivityManager;
@Mock
TelecomManager mTelecomManager;
@Mock
Resources mResources;
@Mock
RankingHandler mRankingHandler;
@@ -494,7 +497,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class));
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
mTelecomManager);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -9126,6 +9130,54 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
}
@Test
public void testCallNotificationsBypassBlock() throws Exception {
when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
.thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
Notification.Builder nb = new Notification.Builder(
mContext, mTestNotificationChannel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.addAction(new Notification.Action.Builder(null, "test", null).build());
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mBinderService.setNotificationsEnabledForPackage(
r.getSbn().getPackageName(), r.getUid(), false);
// normal blocked notifications - blocked
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
// just using the style - blocked
Person person = new Person.Builder()
.setName("caller")
.build();
nb.setStyle(Notification.CallStyle.forOngoingCall(
person, mock(PendingIntent.class)));
nb.setFullScreenIntent(mock(PendingIntent.class), true);
sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
// style + managed call - bypasses block
when(mTelecomManager.isInManagedCall()).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
// style + self managed call - bypasses block
when(mTelecomManager.isInSelfManagedCall(
r.getSbn().getPackageName(), r.getUser())).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
}
@Test
public void testGetAllUsersNotificationPermissions_migrationNotEnabled() {
// make sure we don't bother if the migration is not enabled

View File

@@ -97,6 +97,7 @@ import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -379,7 +380,7 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase {
mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper,
mock(UsageStatsManagerInternal.class));
mock(UsageStatsManagerInternal.class), mock(TelecomManager.class));
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);

View File

@@ -52,6 +52,7 @@ import android.content.pm.PackageManagerInternal;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -167,7 +168,7 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class),
mock(MultiRateLimiter.class), mock(PermissionHelper.class),
mock(UsageStatsManagerInternal.class));
mock(UsageStatsManagerInternal.class), mock (TelecomManager.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;