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