Merge "Proxy notification improvements"

This commit is contained in:
TreeHugger Robot
2018-11-26 20:10:53 +00:00
committed by Android (Google) Code Review
6 changed files with 154 additions and 49 deletions

View File

@@ -51,7 +51,7 @@
android:layout_centerVertical="true"
android:layout_toEndOf="@id/pkgicon" />
<TextView
android:id="@+id/pkg_group_divider"
android:id="@+id/pkg_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
@@ -61,7 +61,7 @@
android:layout_centerVertical="true"
android:layout_toEndOf="@id/pkgname" />
<TextView
android:id="@+id/group_name"
android:id="@+id/delegate_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
@@ -70,7 +70,7 @@
android:ellipsize="end"
android:maxLines="1"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/pkg_group_divider" />
android:layout_toEndOf="@id/pkg_divider" />
<!-- 24 dp icon with 16 dp padding all around to mirror notification content margins -->
<ImageButton
android:id="@+id/info"
@@ -101,13 +101,39 @@
android:layout_marginStart="@*android:dimen/notification_content_margin_start"
android:layout_marginEnd="@*android:dimen/notification_content_margin_start"
android:orientation="vertical">
<!-- Channel Name -->
<TextView
android:id="@+id/channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@android:style/TextAppearance.Material.Notification.Title" />
<RelativeLayout
android:id="@+id/names"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:ellipsize="end"
android:maxLines="1"
android:layout_centerVertical="true" />
<TextView
android:id="@+id/pkg_group_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@*android:string/notification_header_divider_symbol"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/group_name" />
<!-- Channel Name -->
<TextView
android:id="@+id/channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@android:style/TextAppearance.Material.Notification.Title"
android:layout_toEndOf="@id/pkg_group_divider"/>
</RelativeLayout>
<!-- Question prompt -->
<TextView
android:id="@+id/block_prompt"

View File

@@ -1574,6 +1574,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 turned off</string>
<!-- Notification: Control panel: Label for the app that posted this notification, if it's not the package that the notification was posted for -->
<string name="notification_delegate_header">via <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] -->
<string name="appops_camera">This app is using the camera.</string>
<!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] -->

View File

@@ -92,6 +92,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private String mPackageName;
private String mAppName;
private int mAppUid;
private String mDelegatePkg;
private int mNumUniqueChannelsInRow;
private NotificationChannel mSingleNotificationChannel;
private int mStartingChannelImportance;
@@ -235,6 +236,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
(mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
mIsForBlockingHelper = isForBlockingHelper;
mAppUid = mSbn.getUid();
mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
mIsNoisy = isNoisy;
@@ -281,26 +283,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
((TextView) findViewById(R.id.pkgname)).setText(mAppName);
// Set group information if this channel has an associated group.
CharSequence groupName = null;
if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
final NotificationChannelGroup notificationChannelGroup =
mINotificationManager.getNotificationChannelGroupForPackage(
mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
if (notificationChannelGroup != null) {
groupName = notificationChannelGroup.getName();
}
}
TextView groupNameView = findViewById(R.id.group_name);
TextView groupDividerView = findViewById(R.id.pkg_group_divider);
if (groupName != null) {
groupNameView.setText(groupName);
groupNameView.setVisibility(View.VISIBLE);
groupDividerView.setVisibility(View.VISIBLE);
} else {
groupNameView.setVisibility(View.GONE);
groupDividerView.setVisibility(View.GONE);
}
// Delegate
bindDelegate();
// Settings button.
final View settingsButton = findViewById(R.id.info);
@@ -320,9 +304,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
private void bindPrompt() {
private void bindPrompt() throws RemoteException {
final TextView blockPrompt = findViewById(R.id.block_prompt);
bindName();
bindGroup();
if (mIsNonblockable) {
blockPrompt.setText(R.string.notification_unblockable_desc);
} else {
@@ -345,6 +330,60 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
private void bindDelegate() {
TextView delegateView = findViewById(R.id.delegate_name);
TextView dividerView = findViewById(R.id.pkg_divider);
CharSequence delegatePkg = null;
if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
// this notification was posted by a delegate!
ApplicationInfo info;
try {
info = mPm.getApplicationInfo(
mDelegatePkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
}
} catch (PackageManager.NameNotFoundException e) { }
}
if (delegatePkg != null) {
delegateView.setText(mContext.getResources().getString(
R.string.notification_delegate_header, delegatePkg));
delegateView.setVisibility(View.VISIBLE);
dividerView.setVisibility(View.VISIBLE);
} else {
delegateView.setVisibility(View.GONE);
dividerView.setVisibility(View.GONE);
}
}
private void bindGroup() throws RemoteException {
// Set group information if this channel has an associated group.
CharSequence groupName = null;
if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
final NotificationChannelGroup notificationChannelGroup =
mINotificationManager.getNotificationChannelGroupForPackage(
mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
if (notificationChannelGroup != null) {
groupName = notificationChannelGroup.getName();
}
}
TextView groupNameView = findViewById(R.id.group_name);
TextView groupDividerView = findViewById(R.id.pkg_group_divider);
if (groupName != null) {
groupNameView.setText(groupName);
groupNameView.setVisibility(View.VISIBLE);
groupDividerView.setVisibility(View.VISIBLE);
} else {
groupNameView.setVisibility(View.GONE);
groupDividerView.setVisibility(View.GONE);
}
}
@VisibleForTesting
void logBlockingHelperCounter(String counterTag) {
if (mIsForBlockingHelper) {

View File

@@ -134,7 +134,7 @@ public class NotificationInfoTest extends SysuiTestCase {
.thenReturn(packageInfo);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = TEST_UID; // non-zero
when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
applicationInfo);
final PackageInfo systemPackageInfo = new PackageInfo();
systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
@@ -205,6 +205,37 @@ public class NotificationInfoTest extends SysuiTestCase {
assertEquals(iconDrawable, iconView.getDrawable());
}
@Test
public void testBindNotification_noDelegate() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
assertEquals(GONE, dividerView.getVisibility());
}
@Test
public void testBindNotification_delegate() throws Exception {
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = 7; // non-zero
when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
applicationInfo);
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
false, IMPORTANCE_DEFAULT, NotificationInfo.ACTION_NONE);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Other"));
final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
assertEquals(VISIBLE, dividerView.getVisibility());
}
@Test
public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,

View File

@@ -4398,19 +4398,20 @@ public class NotificationManagerService extends SystemService {
*
* Has side effects.
*/
private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag,
private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
NotificationRecord r, boolean isAutogroup) {
final String pkg = r.sbn.getPackageName();
final boolean isSystemNotification =
isUidSystemOrPhone(callingUid) || ("android".equals(pkg));
isUidSystemOrPhone(uid) || ("android".equals(pkg));
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
synchronized (mNotificationLock) {
final int callingUid = Binder.getCallingUid();
if (mNotificationsByKey.get(r.sbn.getKey()) == null
&& isCallerInstantApp(pkg, Binder.getCallingUid(), userId)) {
&& isCallerInstantApp(callingUid, userId)) {
// Ephemeral apps have some special constraints for notifications.
// They are not allowed to create new notifications however they are allowed to
// update notifications created by the system (e.g. a foreground service
@@ -6521,24 +6522,28 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
boolean isCallerInstantApp(String pkg, int callingUid, int userId) {
boolean isCallerInstantApp(int callingUid, int userId) {
// System is always allowed to act for ephemeral apps.
if (isUidSystemOrPhone(callingUid)) {
return false;
}
mAppOps.checkPackage(callingUid, pkg);
try {
final String[] pkgs = mPackageManager.getPackagesForUid(callingUid);
if (pkgs == null) {
throw new SecurityException("Unknown uid " + callingUid);
}
final String pkg = pkgs[0];
mAppOps.checkPackage(callingUid, pkg);
ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, userId);
if (ai == null) {
throw new SecurityException("Unknown package " + pkg);
}
return ai.isInstantApp();
} catch (RemoteException re) {
throw new SecurityException("Unknown package " + pkg, re);
throw new SecurityException("Unknown uid " + callingUid, re);
}
}
private void checkCallerIsSameApp(String pkg) {

View File

@@ -68,16 +68,15 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Application;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.IUriGrantsManager;
import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.ITransientNotification;
import android.app.IUriGrantsManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
@@ -100,7 +99,6 @@ import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
import android.provider.Settings.Secure;
import android.service.notification.Adjustment;
@@ -116,7 +114,6 @@ import android.testing.TestableLooper.RunWithLooper;
import android.text.Html;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.statusbar.NotificationVisibility;
@@ -288,6 +285,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG});
// write to a test file; the system file isn't readable from tests
mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -1735,7 +1733,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess() throws Exception {
public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
@@ -3459,11 +3458,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
ApplicationInfo info = new ApplicationInfo();
info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT;
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{"any"});
assertTrue(mService.isCallerInstantApp("any", 45770, 0));
assertTrue(mService.isCallerInstantApp(45770, 0));
info.privateFlags = 0;
assertFalse(mService.isCallerInstantApp("any", 575370, 0));
assertFalse(mService.isCallerInstantApp(575370, 0));
}
@Test
@@ -3472,8 +3472,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT;
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info);
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{"any"});
assertTrue(mService.isCallerInstantApp("any", 68638450, 10));
assertTrue(mService.isCallerInstantApp(68638450, 10));
}
@Test