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