OSDN Git Service

Merge "DO NOT MERGE - Disallow deletion of channels with FGS notifications" into...
authorChris Tate <ctate@android.com>
Tue, 6 Apr 2021 16:22:21 +0000 (16:22 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Tue, 6 Apr 2021 16:22:21 +0000 (16:22 +0000)
1  2 
core/java/android/app/ActivityManagerInternal.java
services/core/java/com/android/server/am/ActiveServices.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/notification/NotificationManagerService.java
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java

@@@ -378,6 -378,21 +378,21 @@@ public abstract class ActivityManagerIn
      public abstract boolean hasRunningForegroundService(int uid, int foregroundServiceType);
  
      /**
+      * Returns {@code true} if the given notification channel currently has a
+      * notification associated with a foreground service.  This is an AMS check
+      * because that is the source of truth for the FGS state.
+      */
+     public abstract boolean hasForegroundServiceNotification(String pkg, @UserIdInt int userId,
+             String channelId);
+     /**
+      * If the given app has any FGSs whose notifications are in the given channel,
+      * stop them.
+      */
+     public abstract void stopForegroundServicesForChannel(String pkg, @UserIdInt int userId,
+             String channelId);
+     /**
       * Registers the specified {@code processObserver} to be notified of future changes to
       * process state.
       */
       * @return true if exists, false otherwise.
       */
      public abstract boolean isPendingTopUid(int uid);
 +
 +    public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long duration);
 +
 +    public abstract boolean isTempAllowlistedForFgsWhileInUse(int uid);
 +
 +    public abstract boolean canAllowWhileInUsePermissionInFgs(int pid, int uid,
 +            @NonNull String packageName);
  }
@@@ -119,6 -119,7 +119,7 @@@ import java.io.StringWriter
  import java.util.ArrayList;
  import java.util.Comparator;
  import java.util.List;
+ import java.util.Objects;
  import java.util.Set;
  import java.util.function.Predicate;
  
@@@ -433,6 -434,45 +434,45 @@@ public final class ActiveServices 
          return smap != null ? smap.mStartingBackground.size() >= mMaxStartingBackground : false;
      }
  
+     boolean hasForegroundServiceNotificationLocked(String pkg, int userId, String channelId) {
+         final ServiceMap smap = mServiceMap.get(userId);
+         if (smap != null) {
+             for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+                 final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+                 if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
+                     if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) {
+                         if (DEBUG_FOREGROUND_SERVICE) {
+                             Slog.d(TAG_SERVICE, "Channel u" + userId + "/pkg=" + pkg
+                                     + "/channelId=" + channelId
+                                     + " has fg service notification");
+                         }
+                         return true;
+                     }
+                 }
+             }
+         }
+         return false;
+     }
+     void stopForegroundServicesForChannelLocked(String pkg, int userId, String channelId) {
+         final ServiceMap smap = mServiceMap.get(userId);
+         if (smap != null) {
+             for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+                 final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+                 if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
+                     if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) {
+                         if (DEBUG_FOREGROUND_SERVICE) {
+                             Slog.d(TAG_SERVICE, "Stopping FGS u" + userId + "/pkg=" + pkg
+                                     + "/channelId=" + channelId
+                                     + " for conversation channel clear");
+                         }
+                         stopServiceLocked(sr);
+                     }
+                 }
+             }
+         }
+     }
      private ServiceMap getServiceMapLocked(int callingUser) {
          ServiceMap smap = mServiceMap.get(callingUser);
          if (smap == null) {
              return true;
          }
  
 -        if (r.app != null) {
 +        if (r != null && r.app != null) {
              ActiveInstrumentation instr = r.app.getActiveInstrumentation();
              if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
                  return true;
              }
          }
  
 -        final boolean hasAllowBackgroundActivityStartsToken = r.app != null
 -                ? !r.app.mAllowBackgroundActivityStartsTokens.isEmpty() : false;
 -        if (hasAllowBackgroundActivityStartsToken) {
 -            return true;
 +        for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
 +            final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
 +            if (pr.uid == callingUid) {
 +                if (!pr.mAllowBackgroundActivityStartsTokens.isEmpty()) {
 +                    return true;
 +                }
 +                if (pr.getWindowProcessController()
 +                        .areBackgroundActivityStartsAllowedByGracePeriodSafe()) {
 +                    return true;
 +                }
 +            }
          }
  
          if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
              return true;
          }
  
 +        if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {
 +            return true;
 +        }
 +
          final boolean isWhiteListedPackage =
                  mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage);
          if (isWhiteListedPackage) {
          }
          return false;
      }
 +
 +    boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
 +            String callingPackage) {
 +        return shouldAllowWhileInUsePermissionInFgsLocked(
 +                callingPackage, callingPid, callingUid, null, null, false);
 +    }
  }
@@@ -1262,13 -1262,6 +1262,13 @@@ public class ActivityManagerService ext
      final PendingTempWhitelists mPendingTempWhitelist = new PendingTempWhitelists(this);
  
      /**
 +     * List of uids that are allowed to have while-in-use permission when FGS is started from
 +     * background.
 +     */
 +    private final FgsWhileInUseTempAllowList mFgsWhileInUseTempAllowList =
 +            new FgsWhileInUseTempAllowList();
 +
 +    /**
       * Information about and control over application operations
       */
      final AppOpsService mAppOpsService;
                          0,
                          new HostingRecord("system"));
                  app.setPersistent(true);
 -                app.pid = MY_PID;
 +                app.pid = app.mPidForCompact = MY_PID;
                  app.getWindowProcessController().setPid(MY_PID);
                  app.maxAdj = ProcessList.SYSTEM_ADJ;
                  app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
          EventLogTags.writeAmProcBound(app.userId, app.pid, app.processName);
  
          app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
 +        synchronized (mOomAdjuster.mCachedAppOptimizer) {
 +            app.mSetAdjForCompact = ProcessList.INVALID_ADJ;
 +        }
          mOomAdjuster.setAttachingSchedGroupLocked(app);
          app.forcingToImportant = null;
          updateProcessForegroundLocked(app, false, 0, false);
          }
  
          @Override
+         public boolean hasForegroundServiceNotification(String pkg, int userId,
+                 String channelId) {
+             synchronized (ActivityManagerService.this) {
+                 return mServices.hasForegroundServiceNotificationLocked(pkg, userId, channelId);
+             }
+         }
+         @Override
+         public void stopForegroundServicesForChannel(String pkg, int userId,
+                 String channelId) {
+             synchronized (ActivityManagerService.this) {
+                 mServices.stopForegroundServicesForChannelLocked(pkg, userId, channelId);
+             }
+         }
+         @Override
          public void registerProcessObserver(IProcessObserver processObserver) {
              ActivityManagerService.this.registerProcessObserver(processObserver);
          }
          public boolean isPendingTopUid(int uid) {
              return mPendingStartActivityUids.isPendingTopUid(uid);
          }
 +
 +        @Override
 +        public void tempAllowWhileInUsePermissionInFgs(int uid, long duration) {
 +            mFgsWhileInUseTempAllowList.add(uid, duration);
 +        }
 +
 +        @Override
 +        public boolean isTempAllowlistedForFgsWhileInUse(int uid) {
 +            return mFgsWhileInUseTempAllowList.isAllowed(uid);
 +        }
 +
 +        @Override
 +        public boolean canAllowWhileInUsePermissionInFgs(int pid, int uid,
 +                @NonNull String packageName) {
 +            synchronized (ActivityManagerService.this) {
 +                return mServices.canAllowWhileInUsePermissionInFgsLocked(pid, uid, packageName);
 +            }
 +        }
      }
  
      long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
@@@ -403,6 -403,7 +403,7 @@@ public class NotificationManagerServic
      private IActivityManager mAm;
      private ActivityTaskManagerInternal mAtm;
      private ActivityManager mActivityManager;
+     private ActivityManagerInternal mAmi;
      private IPackageManager mPackageManager;
      private PackageManager mPackageManagerClient;
      AudioManager mAudioManager;
              DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
              UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
              NotificationHistoryManager historyManager, StatsManager statsManager,
-             TelephonyManager telephonyManager) {
+             TelephonyManager telephonyManager, ActivityManagerInternal ami) {
          mHandler = handler;
          Resources resources = getContext().getResources();
          mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
          mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
          mCompanionManager = companionManager;
          mActivityManager = activityManager;
+         mAmi = ami;
          mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                  ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
          mDpm = dpm;
                  new NotificationHistoryManager(getContext(), handler),
                  mStatsManager = (StatsManager) getContext().getSystemService(
                          Context.STATS_MANAGER),
-                 getContext().getSystemService(TelephonyManager.class));
+                 getContext().getSystemService(TelephonyManager.class),
+                 LocalServices.getService(ActivityManagerInternal.class));
  
          // register for various Intents
          IntentFilter filter = new IntentFilter();
                      pkg, uid, channelId, conversationId, true, includeDeleted);
          }
  
+         // Returns 'true' if the given channel has a notification associated
+         // with an active foreground service.
+         private void enforceDeletingChannelHasNoFgService(String pkg, int userId,
+                 String channelId) {
+             if (mAmi.hasForegroundServiceNotification(pkg, userId, channelId)) {
+                 Slog.w(TAG, "Package u" + userId + "/" + pkg
+                         + " may not delete notification channel '"
+                         + channelId + "' with fg service");
+                 throw new SecurityException("Not allowed to delete channel " + channelId
+                         + " with a foreground service");
+             }
+         }
          @Override
          public void deleteNotificationChannel(String pkg, String channelId) {
              checkCallerIsSystemOrSameApp(pkg);
              final int callingUid = Binder.getCallingUid();
+             final int callingUser = UserHandle.getUserId(callingUid);
              if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                  throw new IllegalArgumentException("Cannot delete default channel");
              }
+             enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
              cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
-                     UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+                     callingUser, REASON_CHANNEL_BANNED, null);
              mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
              mListeners.notifyNotificationChannelChanged(pkg,
                      UserHandle.getUserHandleForUid(callingUid),
          public void deleteConversationNotificationChannels(String pkg, int uid,
                  String conversationId) {
              checkCallerIsSystem();
-             final int callingUid = Binder.getCallingUid();
              List<NotificationChannel> channels =
                      mPreferencesHelper.getNotificationChannelsByConversationId(
                              pkg, uid, conversationId);
              if (!channels.isEmpty()) {
+                 // Preflight for fg service notifications in these channels:  do nothing
+                 // unless they're all eligible
+                 final int appUserId = UserHandle.getUserId(uid);
                  for (NotificationChannel nc : channels) {
+                     final String channelId = nc.getId();
+                     mAmi.stopForegroundServicesForChannel(pkg, appUserId, channelId);
                      cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
-                             UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
-                     mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+                             appUserId, REASON_CHANNEL_BANNED, null);
+                     mPreferencesHelper.deleteNotificationChannel(pkg, uid, channelId);
                      mListeners.notifyNotificationChannelChanged(pkg,
-                             UserHandle.getUserHandleForUid(callingUid),
+                             UserHandle.getUserHandleForUid(uid),
                              mPreferencesHelper.getNotificationChannel(
-                                     pkg, callingUid, nc.getId(), true),
+                                     pkg, uid, channelId, true),
                              NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
                  }
                  handleSavePolicyFile();
              NotificationChannelGroup groupToDelete =
                      mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
              if (groupToDelete != null) {
+                 // Preflight for allowability
+                 final int userId = UserHandle.getUserId(callingUid);
+                 List<NotificationChannel> groupChannels = groupToDelete.getChannels();
+                 for (int i = 0; i < groupChannels.size(); i++) {
+                     enforceDeletingChannelHasNoFgService(pkg, userId,
+                             groupChannels.get(i).getId());
+                 }
                  List<NotificationChannel> deletedChannels =
                          mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
                  for (int i = 0; i < deletedChannels.size(); i++) {
                      final NotificationChannel deletedChannel = deletedChannels.get(i);
                      cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
                              true,
-                             UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+                             userId, REASON_CHANNEL_BANNED,
                              null);
                      mListeners.notifyNotificationChannelChanged(pkg,
                              UserHandle.getUserHandleForUid(callingUid),
              final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
                      REQUEST_CODE_TIMEOUT,
                      new Intent(ACTION_NOTIFICATION_TIMEOUT)
 +                            .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
                              .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
                                      .appendPath(record.getKey()).build())
                              .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                      // so need to check the notification still valide for vibrate.
                      synchronized (mNotificationLock) {
                          if (mNotificationsByKey.get(record.getKey()) != null) {
 -                            // Vibrator checks the appops for the op package, not the caller,
 -                            // so we need to add the bypass dnd flag to be heard. it's ok to
 -                            // always add this flag here because we've already checked that we can
 -                            // bypass dnd
 -                            AudioAttributes.Builder aab =
 -                                    new AudioAttributes.Builder(record.getAudioAttributes())
 -                                    .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY);
 -                            mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(),
 -                                    effect, "Notification (delayed)", aab.build());
 +                            vibrate(record, effect, true);
                          } else {
                              Slog.e(TAG, "No vibration for canceled notification : "
                                      + record.getKey());
                      }
                  }).start();
              } else {
 -                mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getPackageName(),
 -                        effect, "Notification", record.getAudioAttributes());
 +                vibrate(record, effect, false);
              }
              return true;
          } finally{
          }
      }
  
 +    private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) {
 +        // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService
 +        // doesn't have a concept of vibrating on an app's behalf, so add the app information
 +        // to the reason so we can still debug from bugreports
 +        String reason = "Notification (" + record.getSbn().getOpPkg() + " "
 +                + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : "");
 +        mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
 +                effect, reason, record.getAudioAttributes());
 +    }
 +
      private boolean isNotificationForCurrentUser(NotificationRecord record) {
          final int currentUser;
          final long token = Binder.clearCallingIdentity();
@@@ -84,7 -84,6 +84,7 @@@ import static org.mockito.Mockito.when
  
  import android.app.ActivityManager;
  import android.app.ActivityManagerInternal;
 +import android.app.AlarmManager;
  import android.app.AppOpsManager;
  import android.app.AutomaticZenRule;
  import android.app.IActivityManager;
@@@ -173,7 -172,6 +173,7 @@@ import com.android.server.lights.Lights
  import com.android.server.lights.LogicalLight;
  import com.android.server.notification.NotificationManagerService.NotificationAssistants;
  import com.android.server.notification.NotificationManagerService.NotificationListeners;
 +import com.android.server.pm.PackageManagerService;
  import com.android.server.statusbar.StatusBarManagerInternal;
  import com.android.server.uri.UriGrantsManagerInternal;
  import com.android.server.wm.ActivityTaskManagerInternal;
@@@ -287,8 -285,6 +287,8 @@@ public class NotificationManagerService
      NotificationHistoryManager mHistoryManager;
      @Mock
      StatsManager mStatsManager;
 +    @Mock
 +    AlarmManager mAlarmManager;
      NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
      private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
              1 << 30);
          LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
          LocalServices.removeServiceForTest(ActivityManagerInternal.class);
          LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
 +        mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
 +
  
          doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
  
                  mGroupHelper, mAm, mAtm, mAppUsageStats,
                  mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                  mAppOpsManager, mUm, mHistoryManager, mStatsManager,
-                 mock(TelephonyManager.class));
+                 mock(TelephonyManager.class),
+                 mock(ActivityManagerInternal.class));
          mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
  
          mService.setAudioManager(mAudioManager);
      }
  
      @Test
 +    public void testLimitTimeOutBroadcast() {
 +        NotificationChannel channel = new NotificationChannel("id", "name",
 +                NotificationManager.IMPORTANCE_HIGH);
 +        Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
 +                .setContentTitle("foo")
 +                .setSmallIcon(android.R.drawable.sym_def_app_icon)
 +                .setTimeoutAfter(1);
 +
 +        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
 +                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
 +        NotificationRecord r = new NotificationRecord(mContext, sbn, channel);
 +
 +        mService.scheduleTimeoutLocked(r);
 +        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
 +        verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
 +        assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
 +                captor.getValue().getIntent().getPackage());
 +    }
 +
 +    @Test
      public void testDefaultAssistant_overrideDefault() {
          final int userId = 0;
          final String testComponent = "package/class";