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