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