• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static com.android.internal.util.Preconditions.checkArgument;
20 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
21 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
22 import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
23 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
24 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
25 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
26 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
27 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
28 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
29 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
30 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
31 import static com.android.window.flags.Flags.balImprovedMetrics;
32 
33 import static java.util.Objects.requireNonNull;
34 
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.app.BackgroundStartPrivileges;
38 import android.app.compat.CompatChanges;
39 import android.compat.annotation.ChangeId;
40 import android.compat.annotation.EnabledSince;
41 import android.compat.annotation.Overridable;
42 import android.content.Context;
43 import android.os.Binder;
44 import android.os.Build;
45 import android.os.IBinder;
46 import android.os.SystemClock;
47 import android.os.UserHandle;
48 import android.util.ArrayMap;
49 import android.util.IntArray;
50 
51 import com.android.internal.annotations.GuardedBy;
52 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
53 
54 import java.io.PrintWriter;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.List;
58 import java.util.function.IntPredicate;
59 
60 /**
61  * A per-process controller to decide whether the process can start activity or foreground service
62  * (especially from background). All methods of this class must be thread safe. The caller does not
63  * need to hold WM lock, e.g. lock contention of WM lock shouldn't happen when starting service.
64  */
65 class BackgroundLaunchProcessController {
66     private static final String TAG =
67             TAG_WITH_CLASS_NAME ? "BackgroundLaunchProcessController" : TAG_ATM;
68 
69     /** If enabled BAL are prevented by default in applications targeting U and later. */
70     @ChangeId
71     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
72     @Overridable
73     private static final long DEFAULT_RESCIND_BAL_FG_PRIVILEGES_BOUND_SERVICE = 261072174;
74 
75     /** It is {@link ActivityTaskManagerService#hasActiveVisibleWindow(int)}. */
76     private final IntPredicate mUidHasActiveVisibleWindowPredicate;
77 
78     private final @Nullable BackgroundActivityStartCallback mBackgroundActivityStartCallback;
79 
80     /**
81      * A set of tokens that currently contribute to this process being temporarily allowed
82      * to start activities even if it's not in the foreground. The values of this map are optional
83      * (can be null) and are used to trace back the grant to the notification token mechanism.
84      */
85     @GuardedBy("this")
86     private @Nullable ArrayMap<Binder, BackgroundStartPrivileges> mBackgroundStartPrivileges;
87 
88     /** Set of UIDs of clients currently bound to this process and opt in to allow this process to
89      * launch background activity.
90      */
91     @GuardedBy("this")
92     private @Nullable IntArray mBalOptInBoundClientUids;
93 
BackgroundLaunchProcessController(@onNull IntPredicate uidHasActiveVisibleWindowPredicate, @Nullable BackgroundActivityStartCallback callback)94     BackgroundLaunchProcessController(@NonNull IntPredicate uidHasActiveVisibleWindowPredicate,
95             @Nullable BackgroundActivityStartCallback callback) {
96         mUidHasActiveVisibleWindowPredicate = uidHasActiveVisibleWindowPredicate;
97         mBackgroundActivityStartCallback = callback;
98     }
99 
BalCheckConfiguration( boolean isCheckingForFgsStart, boolean checkVisibility, boolean checkOtherExemptions, long gracePeriod )100     record BalCheckConfiguration(
101             boolean isCheckingForFgsStart,
102             boolean checkVisibility,
103             boolean checkOtherExemptions,
104             long gracePeriod
105     ) {
106     }
107 
108     /**
109      * Check configuration for foreground service starts.
110      *
111      * The check executes all parts of the BAL checks and uses the same grace period,
112      * so FGS is allowed whenever BAL is allowed.
113      */
114     static final BalCheckConfiguration CHECK_FOR_FGS_START = new BalCheckConfiguration(
115             /* isCheckingForFgsStarts */ true,
116             /* checkVisibility */ true,
117             /* checkOtherExemptions */ true,
118             ACTIVITY_BG_START_GRACE_PERIOD_MS);
119 
areBackgroundActivityStartsAllowed( int pid, int uid, String packageName, int appSwitchState, BalCheckConfiguration checkConfiguration, boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime, long lastActivityLaunchTime, long lastActivityFinishTime)120     BalVerdict areBackgroundActivityStartsAllowed(
121             int pid, int uid, String packageName,
122             int appSwitchState, BalCheckConfiguration checkConfiguration,
123             boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
124             long lastStopAppSwitchesTime, long lastActivityLaunchTime,
125             long lastActivityFinishTime) {
126         // Allow if the proc is instrumenting with background activity starts privs.
127         if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) {
128             return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/
129                     "process instrumenting with background activity starts privileges");
130         }
131         // Allow if the token is explicitly allowed.
132         if (checkConfiguration.checkOtherExemptions) {
133             BalVerdict tokenVerdict = isBackgroundStartAllowedByToken(uid,
134                     packageName, checkConfiguration.isCheckingForFgsStart);
135             if (tokenVerdict.allows()) {
136                 if (!balImprovedMetrics()) {
137                     return new BalVerdict(BAL_ALLOW_PERMISSION, tokenVerdict.toString());
138                 }
139                 return tokenVerdict;
140             }
141         }
142         // Allow if the caller is bound by a UID that's currently foreground.
143         // But still respect the appSwitchState.
144         if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
145                 && isBoundByForegroundUid()) {
146             return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
147                     : BAL_ALLOW_VISIBLE_WINDOW, /*background*/
148                     "process bound by foreground uid");
149         }
150         // Allow if the caller has an activity in any foreground task.
151         if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
152                 && appSwitchState != APP_SWITCH_DISALLOW) {
153             return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/
154                     "process has activity in foreground task");
155         }
156 
157         // If app switching is not allowed, we ignore all the start activity grace period
158         // exception so apps cannot start itself in onPause() after pressing home button.
159         if (checkConfiguration.checkOtherExemptions && appSwitchState == APP_SWITCH_ALLOW) {
160             // Allow if any activity in the caller has either started or finished very recently, and
161             // it must be started or finished after last stop app switches time.
162             if (lastActivityLaunchTime > lastStopAppSwitchesTime
163                     || lastActivityFinishTime > lastStopAppSwitchesTime) {
164                 final long now = SystemClock.uptimeMillis();
165                 long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime,
166                         lastActivityFinishTime);
167                 if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) {
168                     return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/
169                             "within " + checkConfiguration.gracePeriod + "ms grace period ("
170                                     + timeSinceLastStartOrFinish + "ms)");
171                 }
172             }
173         }
174         return BalVerdict.BLOCK;
175     }
176 
177     /**
178      * If there are no tokens, we don't allow *by token*. If there are tokens and
179      * isCheckingForFgsStart is false, we ask the callback if the start is allowed for these tokens,
180      * otherwise if there is no callback we allow.
181      */
isBackgroundStartAllowedByToken(int uid, String packageName, boolean isCheckingForFgsStart)182     private BalVerdict isBackgroundStartAllowedByToken(int uid, String packageName,
183             boolean isCheckingForFgsStart) {
184         synchronized (this) {
185             if (mBackgroundStartPrivileges == null
186                     || mBackgroundStartPrivileges.isEmpty()) {
187                 // no tokens to allow anything
188                 return BalVerdict.BLOCK;
189             }
190             if (isCheckingForFgsStart) {
191                 // check if any token allows foreground service starts
192                 for (int i = mBackgroundStartPrivileges.size(); i-- > 0; ) {
193                     if (mBackgroundStartPrivileges.valueAt(i).allowsBackgroundFgsStarts()) {
194                         return new BalVerdict(BAL_ALLOW_TOKEN, "process allowed by token");
195                     }
196                 }
197                 return BalVerdict.BLOCK;
198             }
199             if (mBackgroundActivityStartCallback == null) {
200                 // without a callback just check if any token allows background activity starts
201                 for (int i = mBackgroundStartPrivileges.size(); i-- > 0; ) {
202                     if (mBackgroundStartPrivileges.valueAt(i)
203                             .allowsBackgroundActivityStarts()) {
204                         return new BalVerdict(BAL_ALLOW_TOKEN, "process allowed by token");
205                     }
206                 }
207                 return BalVerdict.BLOCK;
208             }
209             List<IBinder> binderTokens = getOriginatingTokensThatAllowBal();
210             if (binderTokens.isEmpty()) {
211                 // no tokens to allow anything
212                 return BalVerdict.BLOCK;
213             }
214 
215             // The callback will decide.
216             BackgroundActivityStartCallback.BackgroundActivityStartCallbackResult
217                     activityStartAllowed = mBackgroundActivityStartCallback.isActivityStartAllowed(
218                     binderTokens, uid, packageName);
219             if (!activityStartAllowed.allowed()) {
220                 return BalVerdict.BLOCK;
221             }
222             if (activityStartAllowed.token() == null) {
223                 return new BalVerdict(BAL_ALLOW_TOKEN,
224                         "process allowed by callback (token ignored) tokens: " + binderTokens);
225             }
226             return new BalVerdict(BAL_ALLOW_TOKEN,
227                     "process allowed by callback (token: " + activityStartAllowed.token()
228                             + ") tokens: " + binderTokens);
229         }
230     }
231 
getOriginatingTokensThatAllowBal()232     private List<IBinder> getOriginatingTokensThatAllowBal() {
233         List<IBinder> originatingTokens = new ArrayList<>();
234         for (int i = mBackgroundStartPrivileges.size(); i-- > 0; ) {
235             BackgroundStartPrivileges privilege =
236                     mBackgroundStartPrivileges.valueAt(i);
237             if (privilege.allowsBackgroundActivityStarts()) {
238                 originatingTokens.add(privilege.getOriginatingToken());
239             }
240         }
241         return originatingTokens;
242     }
243 
isBoundByForegroundUid()244     private boolean isBoundByForegroundUid() {
245         synchronized (this) {
246             if (mBalOptInBoundClientUids != null) {
247                 for (int i = mBalOptInBoundClientUids.size() - 1; i >= 0; i--) {
248                     if (mUidHasActiveVisibleWindowPredicate.test(mBalOptInBoundClientUids.get(i))) {
249                         return true;
250                     }
251                 }
252             }
253         }
254         return false;
255     }
256 
clearBalOptInBoundClientUids()257     void clearBalOptInBoundClientUids() {
258         synchronized (this) {
259             if (mBalOptInBoundClientUids == null) {
260                 mBalOptInBoundClientUids = new IntArray();
261             } else {
262                 mBalOptInBoundClientUids.clear();
263             }
264         }
265     }
266 
addBoundClientUid(int clientUid, String clientPackageName, long bindFlags)267     void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) {
268         if (!CompatChanges.isChangeEnabled(
269                 DEFAULT_RESCIND_BAL_FG_PRIVILEGES_BOUND_SERVICE,
270                 clientPackageName,
271                 UserHandle.getUserHandleForUid(clientUid))
272                 || (bindFlags & Context.BIND_ALLOW_ACTIVITY_STARTS) != 0) {
273             if (mBalOptInBoundClientUids == null) {
274                 mBalOptInBoundClientUids = new IntArray();
275             }
276             if (mBalOptInBoundClientUids.indexOf(clientUid) == -1) {
277                 mBalOptInBoundClientUids.add(clientUid);
278             }
279         }
280     }
281 
282     /**
283      * Allows background activity starts using token {@code entity}. Optionally, you can provide
284      * {@code originatingToken} in the {@link BackgroundStartPrivileges} if you have one such
285      * originating token, this is useful for tracing back the grant in the case of the notification
286      * token.
287      *
288      * If {@code entity} is already added, this method will update its {@code originatingToken}.
289      */
addOrUpdateAllowBackgroundStartPrivileges(@onNull Binder entity, @NonNull BackgroundStartPrivileges backgroundStartPrivileges)290     void addOrUpdateAllowBackgroundStartPrivileges(@NonNull Binder entity,
291             @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
292         requireNonNull(entity, "entity");
293         requireNonNull(backgroundStartPrivileges, "backgroundStartPrivileges");
294         checkArgument(backgroundStartPrivileges.allowsAny(),
295                 "backgroundStartPrivileges does not allow anything");
296         synchronized (this) {
297             if (mBackgroundStartPrivileges == null) {
298                 mBackgroundStartPrivileges = new ArrayMap<>();
299             }
300             mBackgroundStartPrivileges.put(entity, backgroundStartPrivileges);
301         }
302     }
303 
304     /**
305      * Removes token {@code entity} that allowed background activity starts added via {@link
306      * #addOrUpdateAllowBackgroundStartPrivileges(Binder, BackgroundStartPrivileges)}.
307      */
removeAllowBackgroundStartPrivileges(@onNull Binder entity)308     void removeAllowBackgroundStartPrivileges(@NonNull Binder entity) {
309         requireNonNull(entity, "entity");
310         synchronized (this) {
311             if (mBackgroundStartPrivileges != null) {
312                 mBackgroundStartPrivileges.remove(entity);
313             }
314         }
315     }
316 
317     /**
318      * Returns whether this process is allowed to close system dialogs via a background activity
319      * start token that allows the close system dialogs operation (eg. notification).
320      */
canCloseSystemDialogsByToken(int uid)321     boolean canCloseSystemDialogsByToken(int uid) {
322         if (mBackgroundActivityStartCallback == null) {
323             return false;
324         }
325         synchronized (this) {
326             if (mBackgroundStartPrivileges == null
327                     || mBackgroundStartPrivileges.isEmpty()) {
328                 return false;
329             }
330             return mBackgroundActivityStartCallback.canCloseSystemDialogs(
331                     getOriginatingTokensThatAllowBal(), uid);
332         }
333     }
334 
dump(PrintWriter pw, String prefix)335     void dump(PrintWriter pw, String prefix) {
336         synchronized (this) {
337             if (mBackgroundStartPrivileges != null
338                     && !mBackgroundStartPrivileges.isEmpty()) {
339                 pw.print(prefix);
340                 pw.println("Background activity start tokens (token: originating token):");
341                 for (int i = mBackgroundStartPrivileges.size() - 1; i >= 0; i--) {
342                     pw.print(prefix);
343                     pw.print("  - ");
344                     pw.print(mBackgroundStartPrivileges.keyAt(i));
345                     pw.print(": ");
346                     pw.println(mBackgroundStartPrivileges.valueAt(i));
347                 }
348             }
349             if (mBalOptInBoundClientUids != null && mBalOptInBoundClientUids.size() > 0) {
350                 pw.print(prefix);
351                 pw.print("BoundClientUids:");
352                 pw.println(Arrays.toString(mBalOptInBoundClientUids.toArray()));
353             }
354         }
355     }
356 }
357