• 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.car.am;
18 
19 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
20 import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_DISPLAY_ID;
21 
22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
23 
24 import android.app.ActivityManager;
25 import android.app.ActivityOptions;
26 import android.app.TaskInfo;
27 import android.car.Car;
28 import android.car.app.CarActivityManager;
29 import android.car.app.ICarActivityService;
30 import android.car.builtin.app.ActivityManagerHelper;
31 import android.car.builtin.app.TaskInfoHelper;
32 import android.car.builtin.content.ContextHelper;
33 import android.car.builtin.os.UserManagerHelper;
34 import android.car.builtin.util.Slogf;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.PackageManager;
39 import android.os.Binder;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.os.IBinder;
43 import android.os.RemoteException;
44 import android.os.UserHandle;
45 import android.util.ArrayMap;
46 import android.util.Log;
47 import android.view.Display;
48 
49 import com.android.car.CarLog;
50 import com.android.car.CarServiceBase;
51 import com.android.car.CarServiceUtils;
52 import com.android.car.SystemActivityMonitoringService;
53 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
54 import com.android.car.internal.ICarServiceHelper;
55 import com.android.car.internal.util.IndentingPrintWriter;
56 import com.android.internal.annotations.GuardedBy;
57 import com.android.internal.annotations.VisibleForTesting;
58 
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.LinkedHashMap;
62 import java.util.List;
63 import java.util.Objects;
64 
65 /**
66  * Service responsible for Activities in Car.
67  */
68 public final class CarActivityService extends ICarActivityService.Stub
69         implements CarServiceBase {
70 
71     private static final String TAG = CarLog.TAG_AM;
72     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
73 
74     private final Context mContext;
75 
76     private final Object mLock = new Object();
77 
78     @GuardedBy("mLock")
79     ICarServiceHelper mICarServiceHelper;
80 
81     // LinkedHashMap is used instead of SparseXXX because a predictable iteration order is needed.
82     // The tasks here need be ordered as per their stack order. The stack order is maintained
83     // using a combination of onTaskAppeared and onTaskInfoChanged callbacks.
84     @GuardedBy("mLock")
85     private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mTasks =
86             new LinkedHashMap<>();
87     @GuardedBy("mLock")
88     private final ArrayMap<IBinder, IBinder.DeathRecipient> mTokens = new ArrayMap<>();
89     @GuardedBy("mLock")
90     private IBinder mCurrentMonitor;
91 
92     public interface ActivityLaunchListener {
93         /**
94          * Notify launch of activity.
95          *
96          * @param topTask Task information for what is currently launched.
97          */
onActivityLaunch(TaskInfo topTask)98         void onActivityLaunch(TaskInfo topTask);
99     }
100     @GuardedBy("mLock")
101     private ActivityLaunchListener mActivityLaunchListener;
102 
103     private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread(
104             SystemActivityMonitoringService.class.getSimpleName());
105     private final Handler mHandler = new Handler(mMonitorHandlerThread.getLooper());
106 
CarActivityService(Context context)107     public CarActivityService(Context context) {
108         mContext = context;
109     }
110 
111     @Override
init()112     public void init() {}
113 
114     @Override
release()115     public void release() {}
116 
117     /**
118      * Sets {@code ICarServiceHelper}.
119      */
setICarServiceHelper(ICarServiceHelper helper)120     public void setICarServiceHelper(ICarServiceHelper helper) {
121         synchronized (mLock) {
122             mICarServiceHelper = helper;
123         }
124     }
125 
126     @Override
setPersistentActivity(ComponentName activity, int displayId, int featureId)127     public int setPersistentActivity(ComponentName activity, int displayId, int featureId) throws
128             RemoteException {
129         if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
130                 Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)) {
131             throw new SecurityException("Requires " + Car.PERMISSION_CONTROL_CAR_APP_LAUNCH);
132         }
133         int caller = getCaller();
134         if (caller != UserManagerHelper.USER_SYSTEM && caller != ActivityManager.getCurrentUser()) {
135             return CarActivityManager.RESULT_INVALID_USER;
136         }
137 
138         ICarServiceHelper helper;
139         synchronized (mLock) {
140             helper = mICarServiceHelper;
141         }
142         if (helper == null) {
143             throw new IllegalStateException("ICarServiceHelper isn't connected yet");
144         }
145         return helper.setPersistentActivity(activity, displayId, featureId);
146     }
147 
148     @VisibleForTesting
getCaller()149     int getCaller() {  // Non static for mocking.
150         return UserManagerHelper.getUserId(Binder.getCallingUid());
151     }
152 
registerActivityLaunchListener(ActivityLaunchListener listener)153     public void registerActivityLaunchListener(ActivityLaunchListener listener) {
154         synchronized (mLock) {
155             mActivityLaunchListener = listener;
156         }
157     }
158 
159     @Override
registerTaskMonitor(IBinder token)160     public void registerTaskMonitor(IBinder token) {
161         if (DBG) Slogf.d(TAG, "registerTaskMonitor: %s", token);
162         ensurePermission();
163 
164         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
165             @Override
166             public void binderDied() {
167                 cleanUpToken(token);
168             }
169         };
170         synchronized (mLock) {
171             try {
172                 token.linkToDeath(deathRecipient, /* flags= */ 0);
173             } catch (RemoteException e) {
174                 // 'token' owner might be dead already.
175                 Slogf.e(TAG, "failed to linkToDeath: %s", token);
176                 return;
177             }
178             mTokens.put(token, deathRecipient);
179             mCurrentMonitor = token;
180             // When new TaskOrganizer takes the control, it'll get the status of the whole tasks
181             // in the system again. So drops the old status.
182             mTasks.clear();
183         }
184     }
185 
ensurePermission()186     private void ensurePermission() {
187         if (mContext.checkCallingOrSelfPermission(MANAGE_ACTIVITY_TASKS)
188                 != PackageManager.PERMISSION_GRANTED) {
189             throw new SecurityException("requires permission " + MANAGE_ACTIVITY_TASKS);
190         }
191     }
192 
cleanUpToken(IBinder token)193     private void cleanUpToken(IBinder token) {
194         synchronized (mLock) {
195             if (mCurrentMonitor == token) {
196                 mCurrentMonitor = null;
197             }
198             IBinder.DeathRecipient deathRecipient = mTokens.remove(token);
199             if (deathRecipient != null) {
200                 token.unlinkToDeath(deathRecipient, /* flags= */ 0);
201             }
202         }
203     }
204 
205     @Override
onTaskAppeared(IBinder token, ActivityManager.RunningTaskInfo taskInfo)206     public void onTaskAppeared(IBinder token, ActivityManager.RunningTaskInfo taskInfo) {
207         if (DBG) {
208             Slogf.d(TAG, "onTaskAppeared: %s, %s", token, TaskInfoHelper.toString(taskInfo));
209         }
210         ensurePermission();
211         synchronized (mLock) {
212             if (!isAllowedToUpdateLocked(token)) {
213                 return;
214             }
215             mTasks.put(taskInfo.taskId, taskInfo);
216         }
217         if (TaskInfoHelper.isVisible(taskInfo)) {
218             mHandler.post(() -> notifyActivityLaunch(taskInfo));
219         }
220     }
221 
notifyActivityLaunch(TaskInfo taskInfo)222     private void notifyActivityLaunch(TaskInfo taskInfo) {
223         ActivityLaunchListener listener;
224         synchronized (mLock) {
225             listener = mActivityLaunchListener;
226         }
227         if (listener != null) {
228             listener.onActivityLaunch(taskInfo);
229         }
230     }
231 
232     @GuardedBy("mLock")
isAllowedToUpdateLocked(IBinder token)233     private boolean isAllowedToUpdateLocked(IBinder token) {
234         if (mCurrentMonitor == token) {
235             return true;
236         }
237         // Fallback during no current Monitor exists.
238         boolean allowed = (mCurrentMonitor == null && mTokens.containsKey(token));
239         if (!allowed) {
240             Slogf.w(TAG, "Report with the invalid token: %s", token);
241         }
242         return allowed;
243     }
244 
245     @Override
onTaskVanished(IBinder token, ActivityManager.RunningTaskInfo taskInfo)246     public void onTaskVanished(IBinder token, ActivityManager.RunningTaskInfo taskInfo) {
247         if (DBG) {
248             Slogf.d(TAG, "onTaskVanished: %s, %s", token, TaskInfoHelper.toString(taskInfo));
249         }
250         ensurePermission();
251         synchronized (mLock) {
252             if (!isAllowedToUpdateLocked(token)) {
253                 return;
254             }
255             mTasks.remove(taskInfo.taskId);
256         }
257     }
258 
259     @Override
onTaskInfoChanged(IBinder token, ActivityManager.RunningTaskInfo taskInfo)260     public void onTaskInfoChanged(IBinder token, ActivityManager.RunningTaskInfo taskInfo) {
261         if (DBG) {
262             Slogf.d(TAG, "onTaskInfoChanged: %s, %s", token, TaskInfoHelper.toString(taskInfo));
263         }
264         ensurePermission();
265         synchronized (mLock) {
266             if (!isAllowedToUpdateLocked(token)) {
267                 return;
268             }
269             // The key should be removed and added again so that it jumps to the front of the
270             // LinkedHashMap.
271             TaskInfo oldTaskInfo = mTasks.remove(taskInfo.taskId);
272             mTasks.put(taskInfo.taskId, taskInfo);
273             if ((oldTaskInfo == null || !TaskInfoHelper.isVisible(oldTaskInfo)
274                     || !Objects.equals(oldTaskInfo.topActivity, taskInfo.topActivity))
275                     && TaskInfoHelper.isVisible(taskInfo)) {
276                 mHandler.post(() -> notifyActivityLaunch(taskInfo));
277             }
278         }
279     }
280 
281     @Override
unregisterTaskMonitor(IBinder token)282     public void unregisterTaskMonitor(IBinder token) {
283         if (DBG) Slogf.d(TAG, "unregisterTaskMonitor: %s", token);
284         ensurePermission();
285         cleanUpToken(token);
286     }
287 
288     /**
289      * Returns all the visible tasks. The order is not guaranteed.
290      */
291     @Override
getVisibleTasks()292     public List<ActivityManager.RunningTaskInfo> getVisibleTasks() {
293         ArrayList<ActivityManager.RunningTaskInfo> tasksToReturn = new ArrayList<>();
294         synchronized (mLock) {
295             for (ActivityManager.RunningTaskInfo taskInfo : mTasks.values()) {
296                 // Activities launched in the private display or non-focusable display can't be
297                 // focusable. So we just monitor all visible Activities/Tasks.
298                 if (TaskInfoHelper.isVisible(taskInfo)) {
299                     tasksToReturn.add(taskInfo);
300                 }
301             }
302         }
303         // Reverse the order so that the resultant order is top to bottom.
304         Collections.reverse(tasksToReturn);
305         return tasksToReturn;
306     }
307 
308     /**
309      * Attempts to restart a task.
310      *
311      * <p>Restarts a task by sending an empty intent with flag
312      * {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} to its root activity. If the task does not exist, do
313      * nothing.
314      *
315      * @param taskId id of task to be restarted.
316      */
restartTask(int taskId)317     public void restartTask(int taskId) {
318         TaskInfo task;
319         synchronized (mLock) {
320             task = mTasks.get(taskId);
321         }
322         if (task == null) {
323             Slogf.e(CarLog.TAG_AM, "Could not find root activity with task id " + taskId);
324             return;
325         }
326 
327         Intent intent = (Intent) task.baseIntent.clone();
328         // Clear the task the root activity is running in and start it in a new task.
329         // Effectively restart root activity.
330         intent.addFlags(
331                 Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
332 
333         int userId = TaskInfoHelper.getUserId(task);
334         if (Slogf.isLoggable(CarLog.TAG_AM, Log.INFO)) {
335             Slogf.i(CarLog.TAG_AM, "restarting root activity with user id " + userId);
336         }
337         mContext.startActivityAsUser(intent, UserHandle.of(userId));
338     }
339 
getTaskInfoForTopActivity(ComponentName activity)340     public TaskInfo getTaskInfoForTopActivity(ComponentName activity) {
341         synchronized (mLock) {
342             for (ActivityManager.RunningTaskInfo info : mTasks.values()) {
343                 if (activity.equals(info.topActivity)) {
344                     return info;
345                 }
346             }
347         }
348         return null;
349     }
350 
351     /**
352      * Block the current task: Launch new activity with given Intent and finish the current task.
353      *
354      * @param currentTask task to finish
355      * @param newActivityIntent Intent for new Activity
356      */
blockActivity(TaskInfo currentTask, Intent newActivityIntent)357     public void blockActivity(TaskInfo currentTask, Intent newActivityIntent) {
358         mHandler.post(() -> handleBlockActivity(currentTask, newActivityIntent));
359     }
360 
361     /**
362      * block the current task with the provided new activity.
363      */
handleBlockActivity(TaskInfo currentTask, Intent newActivityIntent)364     private void handleBlockActivity(TaskInfo currentTask, Intent newActivityIntent) {
365         int displayId = newActivityIntent.getIntExtra(BLOCKING_INTENT_EXTRA_DISPLAY_ID,
366                 Display.DEFAULT_DISPLAY);
367         if (Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
368             Slogf.d(CarLog.TAG_AM, "Launching blocking activity on display: " + displayId);
369         }
370 
371         ActivityOptions options = ActivityOptions.makeBasic();
372         options.setLaunchDisplayId(displayId);
373         ContextHelper.startActivityAsUser(mContext, newActivityIntent, options.toBundle(),
374                 UserHandle.of(TaskInfoHelper.getUserId(currentTask)));
375         // Now make stack with new activity focused.
376         findTaskAndGrantFocus(newActivityIntent.getComponent());
377     }
378 
findTaskAndGrantFocus(ComponentName activity)379     private void findTaskAndGrantFocus(ComponentName activity) {
380         TaskInfo taskInfo = getTaskInfoForTopActivity(activity);
381         if (taskInfo != null) {
382             ActivityManagerHelper.setFocusedRootTask(taskInfo.taskId);
383             return;
384         }
385         Slogf.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity);
386     }
387 
388     @Override
389     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)390     public void dump(IndentingPrintWriter writer) {
391         writer.println("*CarActivityService*");
392         writer.println(" Tasks:");
393         synchronized (mLock) {
394             for (ActivityManager.RunningTaskInfo taskInfo : mTasks.values()) {
395                 writer.println("  " + TaskInfoHelper.toString(taskInfo));
396             }
397         }
398     }
399 }
400