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