1 /* 2 * Copyright (C) 2014 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.systemui; 18 19 import android.annotation.SuppressLint; 20 import android.app.ActivityThread; 21 import android.app.Application; 22 import android.app.Notification; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.ApplicationInfo; 28 import android.content.res.Configuration; 29 import android.os.Bundle; 30 import android.os.Process; 31 import android.os.Trace; 32 import android.util.Log; 33 import android.util.TimingsTraceLog; 34 import android.view.SurfaceControl; 35 import android.view.ThreadedRenderer; 36 import android.view.View; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.VisibleForTesting; 40 41 import com.android.internal.protolog.common.ProtoLog; 42 import com.android.systemui.dagger.GlobalRootComponent; 43 import com.android.systemui.dagger.SysUIComponent; 44 import com.android.systemui.dump.DumpManager; 45 import com.android.systemui.process.ProcessWrapper; 46 import com.android.systemui.res.R; 47 import com.android.systemui.statusbar.policy.ConfigurationController; 48 import com.android.systemui.util.NotificationChannels; 49 50 import java.lang.reflect.InvocationTargetException; 51 import java.util.ArrayDeque; 52 import java.util.Comparator; 53 import java.util.HashSet; 54 import java.util.Map; 55 import java.util.Set; 56 import java.util.StringJoiner; 57 import java.util.TreeMap; 58 59 import javax.inject.Provider; 60 61 /** 62 * Application class for SystemUI. 63 */ 64 public class SystemUIApplication extends Application implements 65 SystemUIAppComponentFactoryBase.ContextInitializer { 66 67 public static final String TAG = "SystemUIService"; 68 private static final boolean DEBUG = false; 69 70 private BootCompleteCacheImpl mBootCompleteCache; 71 72 /** 73 * Hold a reference on the stuff we start. 74 */ 75 private CoreStartable[] mServices; 76 private boolean mServicesStarted; 77 private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback; 78 private SysUIComponent mSysUIComponent; 79 private SystemUIInitializer mInitializer; 80 private ProcessWrapper mProcessWrapper; 81 SystemUIApplication()82 public SystemUIApplication() { 83 super(); 84 Trace.registerWithPerfetto(); 85 Log.v(TAG, "SystemUIApplication constructed."); 86 // SysUI may be building without protolog preprocessing in some cases 87 ProtoLog.REQUIRE_PROTOLOGTOOL = false; 88 } 89 90 @VisibleForTesting 91 @Override attachBaseContext(Context base)92 public void attachBaseContext(Context base) { 93 super.attachBaseContext(base); 94 } 95 getRootComponent()96 protected GlobalRootComponent getRootComponent() { 97 return mInitializer.getRootComponent(); 98 } 99 100 @SuppressLint("RegisterReceiverViaContext") 101 @Override onCreate()102 public void onCreate() { 103 super.onCreate(); 104 Log.v(TAG, "SystemUIApplication created."); 105 // This line is used to setup Dagger's dependency injection and should be kept at the 106 // top of this method. 107 TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", 108 Trace.TRACE_TAG_APP); 109 log.traceBegin("DependencyInjection"); 110 mInitializer = mContextAvailableCallback.onContextAvailable(this); 111 mSysUIComponent = mInitializer.getSysUIComponent(); 112 mBootCompleteCache = mSysUIComponent.provideBootCacheImpl(); 113 log.traceEnd(); 114 115 GlobalRootComponent rootComponent = mInitializer.getRootComponent(); 116 117 // Enable Looper trace points. 118 // This allows us to see Handler callbacks on traces. 119 rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP); 120 mProcessWrapper = rootComponent.getProcessWrapper(); 121 122 // Set the application theme that is inherited by all services. Note that setting the 123 // application theme in the manifest does only work for activities. Keep this in sync with 124 // the theme set there. 125 setTheme(R.style.Theme_SystemUI); 126 127 View.setTraceLayoutSteps( 128 rootComponent.getSystemPropertiesHelper() 129 .getBoolean("persist.debug.trace_layouts", false)); 130 View.setTracedRequestLayoutClassClass( 131 rootComponent.getSystemPropertiesHelper() 132 .get("persist.debug.trace_request_layout_class", null)); 133 134 if (Flags.enableLayoutTracing()) { 135 View.setTraceLayoutSteps(true); 136 } 137 138 if (mProcessWrapper.isSystemUser()) { 139 IntentFilter bootCompletedFilter = new 140 IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED); 141 bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 142 143 // If SF GPU context priority is set to realtime, then SysUI should run at high. 144 // The priority is defaulted at medium. 145 int sfPriority = SurfaceControl.getGPUContextPriority(); 146 Log.i(TAG, "Found SurfaceFlinger's GPU Priority: " + sfPriority); 147 if (sfPriority == ThreadedRenderer.EGL_CONTEXT_PRIORITY_REALTIME_NV) { 148 Log.i(TAG, "Setting SysUI's GPU Context priority to: " 149 + ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG); 150 ThreadedRenderer.setContextPriority( 151 ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG); 152 } 153 154 registerReceiver(new BroadcastReceiver() { 155 @Override 156 public void onReceive(Context context, Intent intent) { 157 if (mBootCompleteCache.isBootComplete()) return; 158 159 if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received"); 160 unregisterReceiver(this); 161 mBootCompleteCache.setBootComplete(); 162 if (mServicesStarted) { 163 final int N = mServices.length; 164 for (int i = 0; i < N; i++) { 165 notifyBootCompleted(mServices[i]); 166 } 167 } 168 } 169 }, bootCompletedFilter); 170 171 IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); 172 registerReceiver(new BroadcastReceiver() { 173 @Override 174 public void onReceive(Context context, Intent intent) { 175 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { 176 if (!mBootCompleteCache.isBootComplete()) return; 177 // Update names of SystemUi notification channels 178 NotificationChannels.createAll(context); 179 } 180 } 181 }, localeChangedFilter); 182 } else { 183 // We don't need to startServices for sub-process that is doing some tasks. 184 // (screenshots, sweetsweetdesserts or tuner ..) 185 String processName = ActivityThread.currentProcessName(); 186 ApplicationInfo info = getApplicationInfo(); 187 if (processName != null && processName.startsWith(info.processName + ":")) { 188 return; 189 } 190 // For a secondary user, boot-completed will never be called because it has already 191 // been broadcasted on startup for the primary SystemUI process. Instead, for 192 // components which require the SystemUI component to be initialized per-user, we 193 // start those components now for the current non-system user. 194 startSecondaryUserServicesIfNeeded(); 195 } 196 } 197 198 /** 199 * Makes sure that all the CoreStartables are running. If they are already running, this is a 200 * no-op. This is needed to conditionally start all the services, as we only need to have it in 201 * the main process. 202 * <p>This method must only be called from the main thread.</p> 203 */ 204 startSystemUserServicesIfNeeded()205 public void startSystemUserServicesIfNeeded() { 206 if (!shouldStartSystemUserServices()) { 207 Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser"); 208 return; // Per-user startables are handled in #startSystemUserServicesIfNeeded. 209 } 210 final String vendorComponent = mInitializer.getVendorComponent(getResources()); 211 212 // Sort the startables so that we get a deterministic ordering. 213 // TODO: make #start idempotent and require users of CoreStartable to call it. 214 Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( 215 Comparator.comparing(Class::getName)); 216 sortedStartables.putAll(mSysUIComponent.getStartables()); 217 sortedStartables.putAll(mSysUIComponent.getPerUserStartables()); 218 startServicesIfNeeded( 219 sortedStartables, "StartServices", vendorComponent); 220 } 221 222 /** 223 * Ensures that all the Secondary user SystemUI services are running. If they are already 224 * running, this is a no-op. This is needed to conditionally start all the services, as we only 225 * need to have it in the main process. 226 * <p>This method must only be called from the main thread.</p> 227 */ startSecondaryUserServicesIfNeeded()228 void startSecondaryUserServicesIfNeeded() { 229 if (!shouldStartSecondaryUserServices()) { 230 return; // Per-user startables are handled in #startSystemUserServicesIfNeeded. 231 } 232 // Sort the startables so that we get a deterministic ordering. 233 Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( 234 Comparator.comparing(Class::getName)); 235 sortedStartables.putAll(mSysUIComponent.getPerUserStartables()); 236 startServicesIfNeeded( 237 sortedStartables, "StartSecondaryServices", null); 238 } 239 shouldStartSystemUserServices()240 protected boolean shouldStartSystemUserServices() { 241 return mProcessWrapper.isSystemUser(); 242 } 243 shouldStartSecondaryUserServices()244 protected boolean shouldStartSecondaryUserServices() { 245 return !mProcessWrapper.isSystemUser(); 246 } 247 startServicesIfNeeded( Map<Class<?>, Provider<CoreStartable>> startables, String metricsPrefix, String vendorComponent)248 private void startServicesIfNeeded( 249 Map<Class<?>, Provider<CoreStartable>> startables, 250 String metricsPrefix, 251 String vendorComponent) { 252 if (mServicesStarted) { 253 return; 254 } 255 mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)]; 256 257 if (!mBootCompleteCache.isBootComplete()) { 258 // check to see if maybe it was already completed long before we began 259 // see ActivityManagerService.finishBooting() 260 if ("1".equals(getRootComponent().getSystemPropertiesHelper() 261 .get("sys.boot_completed"))) { 262 mBootCompleteCache.setBootComplete(); 263 if (DEBUG) { 264 Log.v(TAG, "BOOT_COMPLETED was already sent"); 265 } 266 } 267 } 268 269 DumpManager dumpManager = mSysUIComponent.createDumpManager(); 270 271 Log.v(TAG, "Starting SystemUI services for user " + 272 Process.myUserHandle().getIdentifier() + "."); 273 TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", 274 Trace.TRACE_TAG_APP); 275 log.traceBegin(metricsPrefix); 276 277 HashSet<Class<?>> startedStartables = new HashSet<>(); 278 279 // Perform a form of topological sort: 280 // 1) Iterate through a queue of all non-started startables 281 // If the startable has all of its dependencies met 282 // - start it 283 // Else 284 // - enqueue it for the next iteration 285 // 2) If anything was started and the "next" queue is not empty, loop back to 1 286 // 3) If we're done looping and there are any non-started startables left, throw an error. 287 // 288 // This "sort" is not very optimized. We assume that most CoreStartables don't have many 289 // dependencies - zero in fact. We assume two or three iterations of this loop will be 290 // enough. If that ever changes, it may be worth revisiting. 291 292 log.traceBegin("Topologically start Core Startables"); 293 boolean startedAny = false; 294 ArrayDeque<Map.Entry<Class<?>, Provider<CoreStartable>>> queue; 295 ArrayDeque<Map.Entry<Class<?>, Provider<CoreStartable>>> nextQueue = 296 new ArrayDeque<>(startables.entrySet()); 297 int numIterations = 0; 298 299 int serviceIndex = 0; 300 301 do { 302 startedAny = false; 303 queue = nextQueue; 304 nextQueue = new ArrayDeque<>(startables.size()); 305 306 while (!queue.isEmpty()) { 307 Map.Entry<Class<?>, Provider<CoreStartable>> entry = queue.removeFirst(); 308 309 Class<?> cls = entry.getKey(); 310 Set<Class<? extends CoreStartable>> deps = 311 mSysUIComponent.getStartableDependencies().get(cls); 312 if (deps == null || startedStartables.containsAll(deps)) { 313 String clsName = cls.getName(); 314 int i = serviceIndex; // Copied to make lambda happy. 315 timeInitialization( 316 clsName, 317 () -> mServices[i] = startStartable(clsName, entry.getValue()), 318 log, 319 metricsPrefix); 320 startedStartables.add(cls); 321 startedAny = true; 322 serviceIndex++; 323 } else { 324 nextQueue.add(entry); 325 } 326 } 327 numIterations++; 328 } while (startedAny && !nextQueue.isEmpty()); // if none were started, stop. 329 330 if (!nextQueue.isEmpty()) { // If some startables were left over, throw an error. 331 while (!nextQueue.isEmpty()) { 332 Map.Entry<Class<?>, Provider<CoreStartable>> entry = nextQueue.removeFirst(); 333 Class<?> cls = entry.getKey(); 334 Set<Class<? extends CoreStartable>> deps = 335 mSysUIComponent.getStartableDependencies().get(cls); 336 StringJoiner stringJoiner = new StringJoiner(", "); 337 for (Class<? extends CoreStartable> c : deps) { 338 if (!startedStartables.contains(c)) { 339 stringJoiner.add(c.getName()); 340 } 341 } 342 Log.e(TAG, "Failed to start " + cls.getName() 343 + ". Missing dependencies: [" + stringJoiner + "]"); 344 } 345 346 throw new RuntimeException("Failed to start all CoreStartables. Check logcat!"); 347 } 348 Log.i(TAG, "Topological CoreStartables completed in " + numIterations + " iterations"); 349 log.traceEnd(); 350 351 if (vendorComponent != null) { 352 timeInitialization( 353 vendorComponent, 354 () -> mServices[mServices.length - 1] = 355 startAdditionalStartable(vendorComponent), 356 log, 357 metricsPrefix); 358 } 359 360 for (serviceIndex = 0; serviceIndex < mServices.length; serviceIndex++) { 361 final CoreStartable service = mServices[serviceIndex]; 362 if (mBootCompleteCache.isBootComplete()) { 363 notifyBootCompleted(service); 364 } 365 366 if (service.isDumpCritical()) { 367 dumpManager.registerCriticalDumpable(service); 368 } else { 369 dumpManager.registerNormalDumpable(service); 370 } 371 } 372 mSysUIComponent.getInitController().executePostInitTasks(); 373 log.traceEnd(); 374 375 mServicesStarted = true; 376 } 377 notifyBootCompleted(CoreStartable coreStartable)378 private static void notifyBootCompleted(CoreStartable coreStartable) { 379 if (Trace.isEnabled()) { 380 Trace.traceBegin( 381 Trace.TRACE_TAG_APP, 382 coreStartable.getClass().getSimpleName() + ".onBootCompleted()"); 383 } 384 coreStartable.onBootCompleted(); 385 Trace.endSection(); 386 } 387 timeInitialization(String clsName, Runnable init, TimingsTraceLog log, String metricsPrefix)388 private static void timeInitialization(String clsName, Runnable init, TimingsTraceLog log, 389 String metricsPrefix) { 390 long ti = System.currentTimeMillis(); 391 log.traceBegin(metricsPrefix + " " + clsName); 392 init.run(); 393 log.traceEnd(); 394 395 // Warn if initialization of component takes too long 396 ti = System.currentTimeMillis() - ti; 397 if (ti > 1000) { 398 Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); 399 } 400 } 401 startAdditionalStartable(String clsName)402 private static CoreStartable startAdditionalStartable(String clsName) { 403 CoreStartable startable; 404 if (DEBUG) Log.d(TAG, "loading: " + clsName); 405 if (Trace.isEnabled()) { 406 Trace.traceBegin( 407 Trace.TRACE_TAG_APP, clsName + ".newInstance()"); 408 } 409 try { 410 startable = (CoreStartable) Class.forName(clsName) 411 .getDeclaredConstructor() 412 .newInstance(); 413 } catch (ClassNotFoundException 414 | IllegalAccessException 415 | InstantiationException 416 | NoSuchMethodException 417 | InvocationTargetException ex) { 418 throw new RuntimeException(ex); 419 } finally { 420 Trace.endSection(); 421 } 422 423 return startStartable(startable); 424 } 425 startStartable(String clsName, Provider<CoreStartable> provider)426 private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) { 427 if (DEBUG) Log.d(TAG, "loading: " + clsName); 428 if (Trace.isEnabled()) { 429 Trace.traceBegin( 430 Trace.TRACE_TAG_APP, "Provider<" + clsName + ">.get()"); 431 } 432 CoreStartable startable = provider.get(); 433 Trace.endSection(); 434 return startStartable(startable); 435 } 436 startStartable(CoreStartable startable)437 private static CoreStartable startStartable(CoreStartable startable) { 438 if (DEBUG) Log.d(TAG, "running: " + startable); 439 if (Trace.isEnabled()) { 440 Trace.traceBegin( 441 Trace.TRACE_TAG_APP, startable.getClass().getSimpleName() + ".start()"); 442 } 443 startable.start(); 444 Trace.endSection(); 445 446 return startable; 447 } 448 449 @Override onConfigurationChanged(@onNull Configuration newConfig)450 public void onConfigurationChanged(@NonNull Configuration newConfig) { 451 if (mServicesStarted) { 452 ConfigurationController configController = mSysUIComponent.getConfigurationController(); 453 if (Trace.isEnabled()) { 454 Trace.traceBegin( 455 Trace.TRACE_TAG_APP, 456 configController.getClass().getSimpleName() + ".onConfigurationChanged()"); 457 } 458 configController.onConfigurationChanged(newConfig); 459 Trace.endSection(); 460 } 461 } 462 getServices()463 public CoreStartable[] getServices() { 464 return mServices; 465 } 466 467 @Override setContextAvailableCallback( @onNull SystemUIAppComponentFactoryBase.ContextAvailableCallback callback)468 public void setContextAvailableCallback( 469 @NonNull SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) { 470 mContextAvailableCallback = callback; 471 } 472 473 /** Update a notifications application name. */ overrideNotificationAppName(Context context, Notification.Builder n, boolean system)474 public static void overrideNotificationAppName(Context context, Notification.Builder n, 475 boolean system) { 476 final Bundle extras = new Bundle(); 477 String appName = system 478 ? context.getString(com.android.internal.R.string.notification_app_name_system) 479 : context.getString(com.android.internal.R.string.notification_app_name_settings); 480 extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName); 481 482 n.addExtras(extras); 483 } 484 } 485