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