1 /*
2  * Copyright (C) 2023 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.healthconnect;
18 
19 import android.annotation.NonNull;
20 import android.app.ActivityManager;
21 import android.content.Context;
22 import android.util.Slog;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.concurrent.LinkedBlockingQueue;
29 import java.util.concurrent.RejectedExecutionException;
30 import java.util.concurrent.ThreadPoolExecutor;
31 import java.util.concurrent.TimeUnit;
32 
33 /**
34  * A scheduler class to schedule task on the most relevant thread-pool.
35  *
36  * @hide
37  */
38 public final class HealthConnectThreadScheduler {
39     private static final int NUM_EXECUTOR_THREADS_INTERNAL_BACKGROUND = 1;
40     private static final long KEEP_ALIVE_TIME_INTERNAL_BACKGROUND = 60L;
41     private static final int NUM_EXECUTOR_THREADS_BACKGROUND = 1;
42     private static final long KEEP_ALIVE_TIME_BACKGROUND = 60L;
43     private static final int NUM_EXECUTOR_THREADS_FOREGROUND = 1;
44     private static final long KEEP_ALIVE_TIME_SHARED = 60L;
45     private static final int NUM_EXECUTOR_THREADS_CONTROLLER = 1;
46     private static final long KEEP_ALIVE_TIME_CONTROLLER = 60L;
47 
48     // Scheduler to run the tasks in a RR fashion based on client package names.
49     private static final HealthConnectRoundRobinScheduler
50             HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER =
51                     new HealthConnectRoundRobinScheduler();
52     private static final String TAG = "HealthConnectScheduler";
53 
54     // Executor to run HC background tasks
55     @VisibleForTesting
56     static volatile ThreadPoolExecutor sBackgroundThreadExecutor =
57             new ThreadPoolExecutor(
58                     NUM_EXECUTOR_THREADS_BACKGROUND,
59                     NUM_EXECUTOR_THREADS_BACKGROUND,
60                     KEEP_ALIVE_TIME_BACKGROUND,
61                     TimeUnit.SECONDS,
62                     new LinkedBlockingQueue<>());
63 
64     // Executor to run HC background tasks
65     @VisibleForTesting
66     static volatile ThreadPoolExecutor sInternalBackgroundExecutor =
67             new ThreadPoolExecutor(
68                     NUM_EXECUTOR_THREADS_INTERNAL_BACKGROUND,
69                     NUM_EXECUTOR_THREADS_INTERNAL_BACKGROUND,
70                     KEEP_ALIVE_TIME_INTERNAL_BACKGROUND,
71                     TimeUnit.SECONDS,
72                     new LinkedBlockingQueue<>());
73 
74     // Executor to run HC tasks for clients
75     @VisibleForTesting
76     static volatile ThreadPoolExecutor sForegroundExecutor =
77             new ThreadPoolExecutor(
78                     NUM_EXECUTOR_THREADS_FOREGROUND,
79                     NUM_EXECUTOR_THREADS_FOREGROUND,
80                     KEEP_ALIVE_TIME_SHARED,
81                     TimeUnit.SECONDS,
82                     new LinkedBlockingQueue<>());
83 
84     // Executor to run HC controller tasks
85     @VisibleForTesting
86     static volatile ThreadPoolExecutor sControllerExecutor =
87             new ThreadPoolExecutor(
88                     NUM_EXECUTOR_THREADS_CONTROLLER,
89                     NUM_EXECUTOR_THREADS_CONTROLLER,
90                     KEEP_ALIVE_TIME_CONTROLLER,
91                     TimeUnit.SECONDS,
92                     new LinkedBlockingQueue<>());
93 
resetThreadPools()94     public static void resetThreadPools() {
95         sInternalBackgroundExecutor =
96                 new ThreadPoolExecutor(
97                         NUM_EXECUTOR_THREADS_INTERNAL_BACKGROUND,
98                         NUM_EXECUTOR_THREADS_INTERNAL_BACKGROUND,
99                         KEEP_ALIVE_TIME_INTERNAL_BACKGROUND,
100                         TimeUnit.SECONDS,
101                         new LinkedBlockingQueue<>());
102 
103         sBackgroundThreadExecutor =
104                 new ThreadPoolExecutor(
105                         NUM_EXECUTOR_THREADS_BACKGROUND,
106                         NUM_EXECUTOR_THREADS_BACKGROUND,
107                         KEEP_ALIVE_TIME_BACKGROUND,
108                         TimeUnit.SECONDS,
109                         new LinkedBlockingQueue<>());
110 
111         sForegroundExecutor =
112                 new ThreadPoolExecutor(
113                         NUM_EXECUTOR_THREADS_FOREGROUND,
114                         NUM_EXECUTOR_THREADS_FOREGROUND,
115                         KEEP_ALIVE_TIME_SHARED,
116                         TimeUnit.SECONDS,
117                         new LinkedBlockingQueue<>());
118 
119         sControllerExecutor =
120                 new ThreadPoolExecutor(
121                         NUM_EXECUTOR_THREADS_CONTROLLER,
122                         NUM_EXECUTOR_THREADS_CONTROLLER,
123                         KEEP_ALIVE_TIME_CONTROLLER,
124                         TimeUnit.SECONDS,
125                         new LinkedBlockingQueue<>());
126         HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER.resume();
127     }
128 
shutdownThreadPools()129     static void shutdownThreadPools() {
130         HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER.killTasksAndPauseScheduler();
131 
132         sInternalBackgroundExecutor.shutdownNow();
133         sBackgroundThreadExecutor.shutdownNow();
134         sForegroundExecutor.shutdownNow();
135         sControllerExecutor.shutdownNow();
136     }
137 
138     /** Schedules the task on the executor dedicated for performing internal tasks */
scheduleInternalTask(Runnable task)139     public static void scheduleInternalTask(Runnable task) {
140         safeExecute(sInternalBackgroundExecutor, getSafeRunnable(task));
141     }
142 
143     /** Schedules the task on the executor dedicated for performing controller tasks */
scheduleControllerTask(Runnable task)144     static void scheduleControllerTask(Runnable task) {
145         safeExecute(sControllerExecutor, getSafeRunnable(task));
146     }
147 
148     /** Schedules the task on the best possible executor based on the parameters */
schedule(Context context, @NonNull Runnable task, int uid, boolean isController)149     static void schedule(Context context, @NonNull Runnable task, int uid, boolean isController) {
150         if (isController) {
151             safeExecute(sControllerExecutor, getSafeRunnable(task));
152             return;
153         }
154 
155         if (isUidInForeground(context, uid)) {
156             safeExecute(
157                     sForegroundExecutor,
158                     getSafeRunnable(
159                             () -> {
160                                 if (!isUidInForeground(context, uid)) {
161                                     // The app is no longer in foreground so move the task to
162                                     // background thread. This is because foreground thread should
163                                     // only be used by the foreground app and since the request of
164                                     // this task is no longer in foreground we don't want it to
165                                     // consume foreground resource anymore.
166                                     HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER.addTask(
167                                             uid, task);
168                                     safeExecute(
169                                             sBackgroundThreadExecutor,
170                                             () ->
171                                                     HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER
172                                                             .getNextTask()
173                                                             .run());
174                                     return;
175                                 }
176 
177                                 task.run();
178                             }));
179         } else {
180             HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER.addTask(uid, task);
181             safeExecute(
182                     sBackgroundThreadExecutor,
183                     getSafeRunnable(
184                             () ->
185                                     HEALTH_CONNECT_BACKGROUND_ROUND_ROBIN_SCHEDULER
186                                             .getNextTask()
187                                             .run()));
188         }
189     }
190 
isUidInForeground(Context context, int uid)191     private static boolean isUidInForeground(Context context, int uid) {
192         ActivityManager activityManager = context.getSystemService(ActivityManager.class);
193         Objects.requireNonNull(activityManager);
194         List<ActivityManager.RunningAppProcessInfo> runningAppProcesses =
195                 activityManager.getRunningAppProcesses();
196         if (runningAppProcesses == null) {
197             return false;
198         }
199         for (ActivityManager.RunningAppProcessInfo info : runningAppProcesses) {
200             if (info.uid == uid
201                     && info.importance
202                             == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
203                 return true;
204             }
205         }
206         return false;
207     }
208 
safeExecute(ThreadPoolExecutor executor, Runnable task)209     private static void safeExecute(ThreadPoolExecutor executor, Runnable task) {
210         try {
211             executor.execute(task);
212         } catch (RejectedExecutionException ex) {
213             // this is to prevent unexpected crashes, see b/325746130
214             Slog.e(TAG, executor + " is shutting down or already terminated!", ex);
215         }
216     }
217 
218     // Makes sure that any exceptions don't end up in system_server.
getSafeRunnable(Runnable task)219     private static Runnable getSafeRunnable(Runnable task) {
220         return () -> {
221             try {
222                 task.run();
223             } catch (Exception e) {
224                 Slog.e(TAG, "Internal task schedule failed", e);
225             }
226         };
227     }
228 }
229