• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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