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