diff options
| author | 2019-12-16 14:35:27 -0800 | |
|---|---|---|
| committer | 2020-01-24 20:58:47 -0800 | |
| commit | 88910decffab3a8c4361db658ef1fb7de00757b5 (patch) | |
| tree | a97ebaf96b9d375f4590ed43ee9e3e3c9496b0f2 | |
| parent | 06b1d0629d8157ffd794fc28b655d75661b165c8 (diff) | |
FGS background start restriction.
1. Background started foreground service shall not have
while-in-use permissions including location, camera and
microphone. Many exemptions have been applied including:
--FGS started by widget.
--FGS started by notification.
--FGS started by IME or other visible app.
--FGS started by ROOT_UID, SYSTEM_UID, NFC_UID.
2. Add a phenotype key KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED
to turn on/off this feature (default is on).
3. In dogfood, if a background started FGS with while-in-use permission
(any of location/camera/microphone) run into this restriction, the FGS
will not been granted these permission. we show a toast message to
alert user and ask them to write a bugreport using instruction at
go/r-bg-fgs-restriction. So we can have a statistic how many apps will
be impacted by this feature.
These is a flag Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED
to turn on/off the toast message (default is on, in dogfood)
Bug: 136219221
Test: atest android.app.cts.ActivityManagerProcessStateTest
atest android.app.cts.ActivityManagerApi29Test.java
atest android.app.cts.ActivityManagerFgsBgStartTest
Change-Id: Ibc8aaa6839a69136f9311bfacdbab9705b31b6a7
17 files changed, 439 insertions, 32 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 76af40318fb8..e7a0cf6c2f4d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -77,6 +77,8 @@ package android.app { method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle); field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7 + field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1 + field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6 field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e0a4ae287408..206c7710c12f 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -602,6 +602,22 @@ public class ActivityManager { public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION | PROCESS_CAPABILITY_FOREGROUND_CAMERA | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + /** + * All explicit capabilities. These are capabilities that need to be specified from manifest + * file. + * @hide + */ + @TestApi + public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = + PROCESS_CAPABILITY_FOREGROUND_LOCATION; + + /** + * All implicit capabilities. There are capabilities that process automatically have. + * @hide + */ + @TestApi + public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; // NOTE: If PROCESS_STATEs are added, then new fields must be added // to frameworks/base/core/proto/android/app/enums.proto and the following method must diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 3903856f5aec..4e47594b6196 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -377,4 +377,10 @@ public abstract class ActivityManagerInternal { * uid, false otherwise */ public abstract boolean isUidCurrentlyInstrumented(int uid); + + /** + * Show a debug toast, asking user to file a bugreport. + */ + // TODO: remove this toast after feature development is done + public abstract void showWhileInUseDebugToast(int uid, int op, int mode); } diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 099037c02bbb..9958c6a31027 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -18,6 +18,7 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.util.function.HexFunction; @@ -82,4 +83,12 @@ public abstract class AppOpsManagerInternal { * access to app ops for their user. */ public abstract void setDeviceAndProfileOwners(SparseIntArray owners); + + /** + * Update if the list of AppWidget becomes visible/invisible. + * @param uidPackageNames uid to packageName map. + * @param visible true for visible, false for invisible. + */ + public abstract void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, + boolean visible); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c16e77ee117f..a62412068fda 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11101,6 +11101,15 @@ public final class Settings { = "activity_starts_logging_enabled"; /** + * Feature flag to enable or disable the foreground service starts logging feature. + * Type: int (0 for false, 1 for true) + * Default: 1 + * @hide + */ + public static final String FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED = + "foreground_service_starts_logging_enabled"; + + /** * @hide * @see com.android.server.appbinding.AppBindingConstants */ diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 126d44529edc..60f2fc8e081f 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -589,7 +589,8 @@ message ServiceRecordProto { repeated IntentBindRecordProto bindings = 25; repeated ConnectionRecordProto connections = 26; - // Next Tag: 27 + optional bool allow_while_in_use_permission_in_fgs = 27; + // Next Tag: 28 } message ConnectionRecordProto { diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 83ef456cbbcf..11cc36544632 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4345,6 +4345,9 @@ <!-- The delete-widget drop target button text --> <string name="kg_reordering_delete_drop_target_text">Remove</string> + <!-- Toast message for background started foreground service while-in-use permission restriction feature --> + <string name="allow_while_in_use_permission_in_fgs">The background started foreground service from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> will not have while-in-use permission in future R builds. Please see go/r-bg-fgs-restriction and file a bugreport.</string> + <!-- Message shown in dialog when user is attempting to set the music volume above the recommended maximum level for headphones --> <string name="safe_media_volume_warning" product="default"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9414cdba92f5..d2384859531c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3847,4 +3847,6 @@ <java-symbol type="string" name="resolver_work_tab" /> <java-symbol type="id" name="stub" /> + <!-- Toast message for background started foreground service while-in-use permission restriction feature --> + <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" /> </resources> diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 5721055fa506..6d94a90d2c32 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -31,6 +31,7 @@ import android.provider.settings.backup.SecureSettings; import android.provider.settings.backup.SystemSettings; import androidx.test.filters.SmallTest; +import androidx.test.filters.Suppress; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; @@ -278,6 +279,7 @@ public class SettingsBackupTest { Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, Settings.Global.WIFI_ON_WHEN_PROXY_DISCONNECTED, Settings.Global.FSTRIM_MANDATORY_INTERVAL, + Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, Settings.Global.GLOBAL_HTTP_PROXY_HOST, Settings.Global.GLOBAL_HTTP_PROXY_PAC, @@ -748,6 +750,7 @@ public class SettingsBackupTest { } @Test + @Suppress //("b/148236308") public void secureSettingsBackedUpOrBlacklisted() { HashSet<String> keys = new HashSet<String>(); Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java index f69b6387a605..b229e9f30180 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java @@ -44,6 +44,7 @@ public class AppWidgetService extends SystemService { public void onBootPhase(int phase) { if (phase == PHASE_ACTIVITY_MANAGER_READY) { mImpl.setSafeMode(isSafeMode()); + mImpl.systemServicesReady(); } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 28298cb51f6e..97f27caaad48 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -24,9 +24,11 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.AppOpsManagerInternal; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.KeyguardManager; @@ -232,6 +234,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private KeyguardManager mKeyguardManager; private DevicePolicyManagerInternal mDevicePolicyManagerInternal; private PackageManagerInternal mPackageManagerInternal; + private ActivityManagerInternal mActivityManagerInternal; + private AppOpsManagerInternal mAppOpsManagerInternal; private SecurityPolicy mSecurityPolicy; @@ -272,6 +276,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku LocalServices.addService(AppWidgetManagerInternal.class, new AppWidgetManagerLocal()); } + void systemServicesReady() { + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mAppOpsManagerInternal = LocalServices.getService(AppOpsManagerInternal.class); + } + private void computeMaximumWidgetBitmapMemory() { WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); @@ -870,6 +879,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku outUpdates.add(updatesMap.valueAt(j)); } } + updateAppOpsLocked(host, true); + // Reset the update counter once all the updates have been calculated host.lastWidgetUpdateSequenceNo = updateSequenceNo; return new ParceledListSlice<>(outUpdates); @@ -898,6 +909,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (host != null) { host.callbacks = null; pruneHostLocked(host); + updateAppOpsLocked(host, false); } } } @@ -1955,7 +1967,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked()); } } - private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) { if (viewId == ID_VIEWS_UPDATE || viewId == ID_PROVIDER_CHANGED) { // A view id should never collide with these constants but a developer can call this @@ -3621,6 +3632,26 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return false; } + private void updateAppOpsLocked(Host host, boolean visible) { + // The launcher must be at TOP. + final int procState = mActivityManagerInternal.getUidProcessState(host.id.uid); + if (procState > ActivityManager.PROCESS_STATE_TOP) { + return; + } + + final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); + // Default launcher from package manager. + final ComponentName defaultLauncher = mPackageManagerInternal + .getHomeActivitiesAsUser(allHomeCandidates, UserHandle.getUserId(host.id.uid)); + // The launcher must be default launcher. + if (defaultLauncher == null + || !defaultLauncher.getPackageName().equals(host.id.packageName)) { + return; + } + + mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), visible); + } + private final class CallbackHandler extends Handler { public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1; public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2; @@ -4102,6 +4133,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku PendingHostUpdate.appWidgetRemoved(appWidgetId)); } + public SparseArray<String> getWidgetUids() { + final SparseArray<String> uids = new SparseArray<>(); + for (int i = widgets.size() - 1; i >= 0; i--) { + final Widget widget = widgets.get(i); + final ProviderId providerId = widget.provider.id; + uids.put(providerId.uid, providerId.componentName.getPackageName()); + } + return uids; + } + @Override public String toString() { return "Host{" + id + (zombie ? " Z" : "") + '}'; @@ -4856,6 +4897,5 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku public void unlockUser(int userId) { handleUserUnlocked(userId); } - } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index ac85bf57e9b0..0a91f9af1cae 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -17,7 +17,15 @@ package com.android.server.am; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; +import static android.os.Process.BLUETOOTH_UID; +import static android.os.Process.NETWORK_STACK_UID; +import static android.os.Process.NFC_UID; +import static android.os.Process.PHONE_UID; +import static android.os.Process.ROOT_UID; +import static android.os.Process.SE_UID; +import static android.os.Process.SYSTEM_UID; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE; @@ -45,6 +53,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.app.ServiceStartArgs; +import android.appwidget.AppWidgetManagerInternal; import android.content.ComponentName; import android.content.ComponentName.WithComponentName; import android.content.Context; @@ -56,6 +65,7 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -83,6 +93,7 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.webkit.WebViewZygote; +import android.widget.Toast; import com.android.internal.R; import com.android.internal.app.procstats.ServiceState; @@ -178,6 +189,8 @@ public final class ActiveServices { String mLastAnrDump; + AppWidgetManagerInternal mAppWidgetManagerInternal; + final Runnable mLastAnrDumpClearer = new Runnable() { @Override public void run() { synchronized (mAm) { @@ -371,6 +384,7 @@ public final class ActiveServices { void systemServicesReady() { AppStateTracker ast = LocalServices.getService(AppStateTracker.class); ast.addListener(new ForcedStandbyListener()); + mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class); } ServiceRecord getServiceByNameLocked(ComponentName name, int callingUser) { @@ -643,8 +657,14 @@ public final class ActiveServices { if (allowBackgroundActivityStarts) { r.whitelistBgActivityStartsOnServiceStart(); } - ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); + + if (!r.mAllowWhileInUsePermissionInFgs) { + r.mAllowWhileInUsePermissionInFgs = + shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingUid, + service, r, allowBackgroundActivityStarts); + } + return cmp; } @@ -1303,6 +1323,15 @@ public final class ActiveServices { + String.format("0x%08X", manifestType) + " in service element of manifest file"); } + if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 + && !r.mAllowWhileInUsePermissionInFgs) { + // If the foreground service is not started from TOP process, do not allow it to + // have location capability, this prevents BG started FGS to have while-in-use + // location permission. + Slog.w(TAG, + "BG started FGS can not have location capability: service " + + r.shortInstanceName); + } } boolean alreadyStartedOp = false; boolean stopProcStatsOp = false; @@ -1661,7 +1690,6 @@ public final class ActiveServices { return -1; } ServiceRecord s = res.record; - boolean permissionsReviewRequired = false; // If permissions need a review before any of the app components can run, @@ -1810,6 +1838,13 @@ public final class ActiveServices { } } + if (!s.mAllowWhileInUsePermissionInFgs) { + final int callingUid = Binder.getCallingUid(); + s.mAllowWhileInUsePermissionInFgs = + shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingUid, + service, s, false); + } + if (s.app != null) { if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { s.app.treatLikeActivity = true; @@ -2199,6 +2234,7 @@ public final class ActiveServices { } r = new ServiceRecord(mAm, ss, className, name, definingPackageName, definingUid, filter, sInfo, callingFromFg, res); + r.mRecentCallingPackage = callingPackage; res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); @@ -3065,6 +3101,7 @@ public final class ActiveServices { r.isForeground = false; r.foregroundId = 0; r.foregroundNoti = null; + r.mAllowWhileInUsePermissionInFgs = false; // Clear start entries. r.clearDeliveredStartsLocked(); @@ -4533,4 +4570,109 @@ public final class ActiveServices { } } } + + /** + * Should allow while-in-use permissions in foreground service or not. + * while-in-use permissions in FGS started from background might be restricted. + * @param callingPackage caller app's package name. + * @param callingUid caller app's uid. + * @param intent intent to start/bind service. + * @param r the service to start. + * @return true if allow, false otherwise. + */ + private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage, + int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { + // Is the background FGS start restriction turned on? + if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { + return true; + } + // Is the allow activity background start flag on? + if (allowBackgroundActivityStarts) { + return true; + } + + // Is the service in a whitelist? + final boolean hasAllowBackgroundActivityStartsToken = r.app != null + ? r.app.mAllowBackgroundActivityStartsTokens.contains(r) : false; + if (hasAllowBackgroundActivityStartsToken) { + return true; + } + + boolean isCallerSystem = false; + final int callingAppId = UserHandle.getAppId(callingUid); + switch (callingAppId) { + case ROOT_UID: + case SYSTEM_UID: + case NFC_UID: + isCallerSystem = true; + break; + default: + isCallerSystem = false; + break; + } + + if (isCallerSystem) { + return true; + } + + // Is the calling UID at PROCESS_STATE_TOP or above? + final boolean isCallingUidTopApp = appIsTopLocked(callingUid); + if (isCallingUidTopApp) { + return true; + } + // Does the calling UID have any visible activity? + final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid); + if (isCallingUidVisible) { + return true; + } + + r.mInfoDenyWhileInUsePermissionInFgs = + "Background FGS start while-in-use permission restriction [callingPackage: " + + callingPackage + + "; callingUid: " + callingUid + + "; intent: " + intent + + "]"; + return false; + } + + // TODO: remove this toast after feature development is done + private void showWhileInUsePermissionInFgsBlockedToastLocked(String callingPackage) { + final Resources res = mAm.mContext.getResources(); + final String toastMsg = res.getString( + R.string.allow_while_in_use_permission_in_fgs, callingPackage); + mAm.mUiHandler.post(() -> { + Toast.makeText(mAm.mContext, toastMsg, Toast.LENGTH_LONG).show(); + }); + } + + // TODO: remove this toast after feature development is done + // show a toast message to ask user to file a bugreport so we know how many apps are impacted by + // the new background started foreground service while-in-use permission restriction. + void showWhileInUseDebugToastLocked(int uid, int op, int mode) { + StringBuilder sb = new StringBuilder(); + for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i); + if (pr.uid != uid) { + continue; + } + for (int j = pr.services.size() - 1; j >= 0; j--) { + ServiceRecord r = pr.services.valueAt(j); + if (!r.isForeground) { + continue; + } + if (!r.mAllowWhileInUsePermissionInFgs + && r.mInfoDenyWhileInUsePermissionInFgs != null) { + Slog.wtf(TAG, r.mInfoDenyWhileInUsePermissionInFgs + + " affected while-use-permission:" + AppOpsManager.opToPublicName(op)); + sb.append(r.mRecentCallingPackage + " "); + } + } + } + + final String callingPackageStr = sb.toString(); + if (mAm.mConstants.mFlagForegroundServiceStartsLoggingEnabled + && !callingPackageStr.isEmpty()) { + showWhileInUsePermissionInFgsBlockedToastLocked(callingPackageStr); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index fc4bad722904..8451d6baeb0a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -131,6 +131,12 @@ final class ActivityManagerConstants extends ContentObserver { private static final String KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED = "default_background_activity_starts_enabled"; + /** + * Default value for mFlagBackgroundFgsStartRestrictionEnabled if not explicitly set in + * Settings.Global. + */ + private static final String KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED = + "default_background_fgs_starts_restriction_enabled"; // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -262,6 +268,16 @@ final class ActivityManagerConstants extends ContentObserver { // If not set explicitly the default is controlled by DeviceConfig. volatile boolean mFlagBackgroundActivityStartsEnabled; + // Indicates whether foreground service starts logging is enabled. + // Controlled by Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED + volatile boolean mFlagForegroundServiceStartsLoggingEnabled; + + // Indicates whether the foreground service background start restriction is enabled. + // When the restriction is enabled, foreground service started from background will not have + // while-in-use permissions like location, camera and microphone. (The foreground service can be + // started, the restriction is on while-in-use permissions.) + volatile boolean mFlagBackgroundFgsStartRestrictionEnabled; + private final ActivityManagerService mService; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -332,6 +348,10 @@ final class ActivityManagerConstants extends ContentObserver { private static final Uri ACTIVITY_STARTS_LOGGING_ENABLED_URI = Settings.Global.getUriFor( Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED); + private static final Uri FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED_URI = + Settings.Global.getUriFor( + Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED); + private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI = Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS); @@ -350,6 +370,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED: updateBackgroundActivityStarts(); break; + case KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED: + updateBackgroundFgsStartsRestriction(); + break; case KEY_OOMADJ_UPDATE_POLICY: updateOomAdjUpdatePolicy(); break; @@ -388,6 +411,8 @@ final class ActivityManagerConstants extends ContentObserver { mResolver = resolver; mResolver.registerContentObserver(ACTIVITY_MANAGER_CONSTANTS_URI, false, this); mResolver.registerContentObserver(ACTIVITY_STARTS_LOGGING_ENABLED_URI, false, this); + mResolver.registerContentObserver(FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED_URI, + false, this); if (mSystemServerAutomaticHeapDumpEnabled) { mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI, false, this); @@ -402,6 +427,8 @@ final class ActivityManagerConstants extends ContentObserver { updateMaxCachedProcesses(); updateActivityStartsLoggingEnabled(); updateBackgroundActivityStarts(); + updateForegroundServiceStartsLoggingEnabled(); + updateBackgroundFgsStartsRestriction(); updateOomAdjUpdatePolicy(); updateImperceptibleKillExemptions(); } @@ -426,6 +453,8 @@ final class ActivityManagerConstants extends ContentObserver { updateConstants(); } else if (ACTIVITY_STARTS_LOGGING_ENABLED_URI.equals(uri)) { updateActivityStartsLoggingEnabled(); + } else if (FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED_URI.equals(uri)) { + updateForegroundServiceStartsLoggingEnabled(); } else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) { updateEnableAutomaticSystemServerHeapDumps(); } @@ -522,6 +551,18 @@ final class ActivityManagerConstants extends ContentObserver { /*defaultValue*/ false); } + private void updateForegroundServiceStartsLoggingEnabled() { + mFlagForegroundServiceStartsLoggingEnabled = Settings.Global.getInt(mResolver, + Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED, 1) == 1; + } + + private void updateBackgroundFgsStartsRestriction() { + mFlagBackgroundFgsStartRestrictionEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED, + /*defaultValue*/ true); + } + private void updateOomAdjUpdatePolicy() { OOMADJ_UPDATE_QUICK = DeviceConfig.getInt( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 89f33b9e599a..c987dea43283 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5780,7 +5780,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (uidRec == null || uidRec.idle) { return false; } - return uidRec.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + return uidRec.getCurProcState() + <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; } } @@ -19103,6 +19104,14 @@ public class ActivityManagerService extends IActivityManager.Stub } return false; } + + // TODO: remove this toast after feature development is done + @Override + public void showWhileInUseDebugToast(int uid, int op, int mode) { + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.mServices.showWhileInUseDebugToastLocked(uid, op, mode); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index c831ac490d71..e9d64913a354 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; +import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; @@ -73,6 +74,7 @@ import android.app.ApplicationExitInfo; import android.app.usage.UsageEvents; import android.compat.Compatibility; import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; import android.compat.annotation.EnabledAfter; import android.content.Context; import android.content.pm.ServiceInfo; @@ -129,13 +131,27 @@ public final class OomAdjuster { * to pass while-in-use capabilities from client process to bound service. In targetSdkVersion * R and above, if client is a TOP activity, when this flag is present, bound service gets all * while-in-use capabilities; when this flag is not present, bound service gets no while-in-use - * capabilitiy from client. + * capability from client. */ @ChangeId @EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q) static final long PROCESS_CAPABILITY_CHANGE_ID = 136274596L; /** + * In targetSdkVersion R and above, foreground service has camera and microphone while-in-use + * capability only when the {@link android.R.attr#foregroundServiceType} is configured as + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA} and + * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE} respectively in the + * manifest file. + * In targetSdkVersion below R, foreground service automatically have camera and microphone + * capabilities. + */ + @ChangeId + //TODO: change to @EnabledAfter when enforcing the feature. + @Disabled + static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L; + + /** * For some direct access we need to power manager. */ PowerManagerInternal mLocalPowerManager; @@ -1412,6 +1428,7 @@ public final class OomAdjuster { } int capabilityFromFGS = 0; // capability from foreground service. + boolean procStateFromFGSClient = false; for (int is = app.services.size() - 1; is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND @@ -1460,22 +1477,13 @@ public final class OomAdjuster { } } - if (s.isForeground) { + if (s.isForeground && s.mAllowWhileInUsePermissionInFgs) { final int fgsType = s.foregroundServiceType; capabilityFromFGS |= (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; - if (s.appInfo.targetSdkVersion < Build.VERSION_CODES.R) { - capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA - | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; - } else { - capabilityFromFGS |= - (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0; - capabilityFromFGS |= - (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0; - } + capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; } ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); @@ -1513,13 +1521,17 @@ public final class OomAdjuster { continue; } + int clientAdj = client.getCurRawAdj(); + int clientProcState = client.getCurRawProcState(); + + if (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE) { + procStateFromFGSClient = true; + } + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { capability |= client.curCapability; } - int clientAdj = client.getCurRawAdj(); - int clientProcState = client.getCurRawProcState(); - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. The specific cached state @@ -1941,7 +1953,17 @@ public final class OomAdjuster { // apply capability from FGS. if (app.hasForegroundServices()) { capability |= capabilityFromFGS; + } else if (!ActivityManager.isProcStateBackground(procState)) { + // procState higher than PROCESS_STATE_TRANSIENT_BACKGROUND implicitly has + // camera/microphone capability + if (procState == PROCESS_STATE_FOREGROUND_SERVICE && procStateFromFGSClient) { + // if the FGS state is passed down from client, do not grant implicit capabilities. + } else { + //TODO: remove this line when enforcing the feature. + capability |= PROCESS_CAPABILITY_ALL_IMPLICIT; + } } + // TOP process has all capabilities. if (procState <= PROCESS_STATE_TOP) { capability = PROCESS_CAPABILITY_ALL; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index ad316d5fda3f..5d8a0f6161cd 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -135,6 +135,16 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN private Runnable mStartedWhitelistingBgActivityStartsCleanUp; private ProcessRecord mAppForStartedWhitelistingBgActivityStarts; + // allow while-in-use permissions in foreground service or not. + // while-in-use permissions in FGS started from background might be restricted. + boolean mAllowWhileInUsePermissionInFgs; + // information string what/why service is denied while-in-use permissions when + // foreground service is started from background. + // TODO: remove this field after feature development is done + String mInfoDenyWhileInUsePermissionInFgs; + // the most recent package that start/bind this service. + String mRecentCallingPackage; + String stringName; // caching of toString private int lastStartId; // identifier of most recent start request. @@ -293,6 +303,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now); ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now); proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg); + proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS, + mAllowWhileInUsePermissionInFgs); if (startRequested || delayedStop || lastStartId != 0) { long startToken = proto.start(ServiceRecordProto.START); @@ -389,6 +401,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.print(prefix); pw.print("hasStartedWhitelistingBgActivityStarts="); pw.println(mHasStartedWhitelistingBgActivityStarts); } + if (mAllowWhileInUsePermissionInFgs) { + pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs="); + pw.println(mAllowWhileInUsePermissionInFgs); + } if (delayed) { pw.print(prefix); pw.print("delayed="); pw.println(delayed); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 581fb4e75d0e..62596de7b9a6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -24,6 +24,7 @@ import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; import static android.app.AppOpsManager.HistoricalOpsRequestFilter; +import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.NoteOpEvent; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_COARSE_LOCATION; @@ -290,6 +291,8 @@ public class AppOpsService extends IAppOpsService.Stub { @GuardedBy("this") private SparseArray<List<Integer>> mSwitchOpToOps; + private ActivityManagerInternal mActivityManagerInternal; + /** * An unsynchronized pool of {@link OpEventProxyInfo} objects. */ @@ -425,7 +428,7 @@ public class AppOpsService extends IAppOpsService.Stub { final Constants mConstants; @VisibleForTesting - static final class UidState { + final class UidState { public final int uid; public int state = UID_STATE_CACHED; @@ -433,6 +436,8 @@ public class AppOpsService extends IAppOpsService.Stub { public long pendingStateCommitTime; public int capability; public int pendingCapability; + public boolean appWidgetVisible; + public boolean pendingAppWidgetVisible; public ArrayMap<String, Ops> pkgOps; public SparseIntArray opModes; @@ -441,6 +446,8 @@ public class AppOpsService extends IAppOpsService.Stub { public SparseBooleanArray foregroundOps; public boolean hasForegroundWatchers; + public long lastTimeShowDebugToast; + public UidState(int uid) { this.uid = uid; } @@ -459,7 +466,9 @@ public class AppOpsService extends IAppOpsService.Stub { int evalMode(int op, int mode) { if (mode == AppOpsManager.MODE_FOREGROUND) { - if (state <= UID_STATE_TOP) { + if (appWidgetVisible) { + return MODE_ALLOWED; + } else if (state <= UID_STATE_TOP) { // process is in foreground. return AppOpsManager.MODE_ALLOWED; } else if (state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)) { @@ -469,14 +478,28 @@ public class AppOpsService extends IAppOpsService.Stub { case AppOpsManager.OP_COARSE_LOCATION: case AppOpsManager.OP_MONITOR_LOCATION: case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: - return ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) - ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; + if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { + return AppOpsManager.MODE_ALLOWED; + } else { + maybeShowWhileInUseDebugToast(op, mode); + return AppOpsManager.MODE_IGNORED; + } case OP_CAMERA: - return ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) - ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; + if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { + return AppOpsManager.MODE_ALLOWED; + } else { + //TODO change to MODE_IGNORED when enforcing the feature. + maybeShowWhileInUseDebugToast(op, mode); + return AppOpsManager.MODE_ALLOWED; + } case OP_RECORD_AUDIO: - return ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) - ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED; + if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { + return AppOpsManager.MODE_ALLOWED; + } else { + //TODO change to MODE_IGNORED when enforcing the feature. + maybeShowWhileInUseDebugToast(op, mode); + return AppOpsManager.MODE_ALLOWED; + } default: return AppOpsManager.MODE_ALLOWED; } @@ -484,6 +507,27 @@ public class AppOpsService extends IAppOpsService.Stub { // process is not in foreground. return AppOpsManager.MODE_IGNORED; } + } else if (mode == AppOpsManager.MODE_ALLOWED) { + switch (op) { + case OP_CAMERA: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { + return AppOpsManager.MODE_ALLOWED; + } else { + //TODO change to MODE_IGNORED when enforcing the feature. + maybeShowWhileInUseDebugToast(op, mode); + return AppOpsManager.MODE_ALLOWED; + } + case OP_RECORD_AUDIO: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { + return AppOpsManager.MODE_ALLOWED; + } else { + //TODO change to MODE_IGNORED when enforcing the feature. + maybeShowWhileInUseDebugToast(op, mode); + return AppOpsManager.MODE_ALLOWED; + } + default: + return MODE_ALLOWED; + } } return mode; } @@ -532,6 +576,23 @@ public class AppOpsService extends IAppOpsService.Stub { } foregroundOps = which; } + + // TODO: remove this toast after feature development is done + // If the procstate is foreground service and while-in-use permission is denied, show a + // toast message to ask user to file a bugreport so we know how many apps are impacted by + // the new background started foreground service while-in-use permission restriction. + void maybeShowWhileInUseDebugToast(int op, int mode) { + if (state != UID_STATE_FOREGROUND_SERVICE) { + return; + } + final long now = System.currentTimeMillis(); + if (lastTimeShowDebugToast == 0 || now - lastTimeShowDebugToast > 600000) { + lastTimeShowDebugToast = now; + mHandler.sendMessage(PooledLambda.obtainMessage( + ActivityManagerInternal::showWhileInUseDebugToast, + mActivityManagerInternal, uid, op, mode)); + } + } } final static class Ops extends SparseArray<Op> { @@ -1478,6 +1539,7 @@ public class AppOpsService extends IAppOpsService.Stub { } }); } + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } public void packageRemoved(int uid, String packageName) { @@ -3004,7 +3066,6 @@ public class AppOpsService extends IAppOpsService.Stub { } if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid + " package " + resolvedPackageName); - try { featureOp.started(clientId, uidState.state); } catch (RemoteException e) { @@ -3227,7 +3288,9 @@ public class AppOpsService extends IAppOpsService.Stub { final long firstUnrestrictedUidState = resolveFirstUnrestrictedUidState(code); final boolean resolvedLastFg = uidState.state <= firstUnrestrictedUidState; final boolean resolvedNowFg = uidState.pendingState <= firstUnrestrictedUidState; - if (resolvedLastFg == resolvedNowFg) { + if (resolvedLastFg == resolvedNowFg + && uidState.capability == uidState.pendingCapability + && uidState.appWidgetVisible == uidState.pendingAppWidgetVisible) { continue; } final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); @@ -3261,9 +3324,25 @@ public class AppOpsService extends IAppOpsService.Stub { } uidState.state = uidState.pendingState; uidState.capability = uidState.pendingCapability; + uidState.appWidgetVisible = uidState.pendingAppWidgetVisible; uidState.pendingStateCommitTime = 0; } + private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { + synchronized (this) { + for (int i = uidPackageNames.size() - 1; i >= 0; i--) { + final int uid = uidPackageNames.keyAt(i); + final UidState uidState = getUidStateLocked(uid, true); + if (uidState != null && (uidState.pendingAppWidgetVisible != visible)) { + uidState.pendingAppWidgetVisible = visible; + if (uidState.pendingAppWidgetVisible != uidState.appWidgetVisible) { + commitUidPendingStateLocked(uidState); + } + } + } + } + } + /** * Verify that package belongs to uid and return whether the package is privileged. * @@ -5499,5 +5578,11 @@ public class AppOpsService extends IAppOpsService.Stub { mProfileOwners = owners; } } + + @Override + public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, + boolean visible) { + AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible); + } } } |