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.app.ActivityThread;
20 import android.app.Application;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ApplicationInfo;
26 import android.content.res.Configuration;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.Process;
30 import android.os.SystemProperties;
31 import android.os.Trace;
32 import android.os.UserHandle;
33 import android.util.ArraySet;
34 import android.util.Log;
35 import android.util.TimingsTraceLog;
36 
37 import com.android.systemui.plugins.OverlayPlugin;
38 import com.android.systemui.plugins.PluginListener;
39 import com.android.systemui.shared.plugins.PluginManager;
40 import com.android.systemui.statusbar.phone.DozeParameters;
41 import com.android.systemui.statusbar.phone.StatusBar;
42 import com.android.systemui.statusbar.phone.StatusBarWindowController;
43 import com.android.systemui.util.NotificationChannels;
44 
45 import java.util.HashMap;
46 import java.util.Map;
47 
48 /**
49  * Application class for SystemUI.
50  */
51 public class SystemUIApplication extends Application implements SysUiServiceProvider {
52 
53     public static final String TAG = "SystemUIService";
54     private static final boolean DEBUG = false;
55 
56     /**
57      * Hold a reference on the stuff we start.
58      */
59     private SystemUI[] mServices;
60     private boolean mServicesStarted;
61     private boolean mBootCompleted;
62     private final Map<Class<?>, Object> mComponents = new HashMap<>();
63     private ContextAvailableCallback mContextAvailableCallback;
64 
SystemUIApplication()65     public SystemUIApplication() {
66         super();
67         Log.v(TAG, "SystemUIApplication constructed.");
68     }
69 
70     @Override
onCreate()71     public void onCreate() {
72         super.onCreate();
73         Log.v(TAG, "SystemUIApplication created.");
74         // This line is used to setup Dagger's dependency injection and should be kept at the
75         // top of this method.
76         TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
77                 Trace.TRACE_TAG_APP);
78         log.traceBegin("DependencyInjection");
79         mContextAvailableCallback.onContextAvailable(this);
80         log.traceEnd();
81 
82         // Set the application theme that is inherited by all services. Note that setting the
83         // application theme in the manifest does only work for activities. Keep this in sync with
84         // the theme set there.
85         setTheme(R.style.Theme_SystemUI);
86 
87         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
88             IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
89             bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
90             registerReceiver(new BroadcastReceiver() {
91                 @Override
92                 public void onReceive(Context context, Intent intent) {
93                     if (mBootCompleted) return;
94 
95                     if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
96                     unregisterReceiver(this);
97                     mBootCompleted = true;
98                     if (mServicesStarted) {
99                         final int N = mServices.length;
100                         for (int i = 0; i < N; i++) {
101                             mServices[i].onBootCompleted();
102                         }
103                     }
104 
105 
106                 }
107             }, bootCompletedFilter);
108 
109             IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
110             registerReceiver(new BroadcastReceiver() {
111                 @Override
112                 public void onReceive(Context context, Intent intent) {
113                     if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
114                         if (!mBootCompleted) return;
115                         // Update names of SystemUi notification channels
116                         NotificationChannels.createAll(context);
117                     }
118                 }
119             }, localeChangedFilter);
120         } else {
121             // We don't need to startServices for sub-process that is doing some tasks.
122             // (screenshots, sweetsweetdesserts or tuner ..)
123             String processName = ActivityThread.currentProcessName();
124             ApplicationInfo info = getApplicationInfo();
125             if (processName != null && processName.startsWith(info.processName + ":")) {
126                 return;
127             }
128             // For a secondary user, boot-completed will never be called because it has already
129             // been broadcasted on startup for the primary SystemUI process.  Instead, for
130             // components which require the SystemUI component to be initialized per-user, we
131             // start those components now for the current non-system user.
132             startSecondaryUserServicesIfNeeded();
133         }
134     }
135 
136     /**
137      * Makes sure that all the SystemUI services are running. If they are already running, this is a
138      * no-op. This is needed to conditinally start all the services, as we only need to have it in
139      * the main process.
140      * <p>This method must only be called from the main thread.</p>
141      */
142 
startServicesIfNeeded()143     public void startServicesIfNeeded() {
144         String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
145         startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
146     }
147 
148     /**
149      * Ensures that all the Secondary user SystemUI services are running. If they are already
150      * running, this is a no-op. This is needed to conditionally start all the services, as we only
151      * need to have it in the main process.
152      * <p>This method must only be called from the main thread.</p>
153      */
startSecondaryUserServicesIfNeeded()154     void startSecondaryUserServicesIfNeeded() {
155         String[] names =
156                   getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
157         startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names);
158     }
159 
startServicesIfNeeded(String metricsPrefix, String[] services)160     private void startServicesIfNeeded(String metricsPrefix, String[] services) {
161         if (mServicesStarted) {
162             return;
163         }
164         mServices = new SystemUI[services.length];
165 
166         if (!mBootCompleted) {
167             // check to see if maybe it was already completed long before we began
168             // see ActivityManagerService.finishBooting()
169             if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
170                 mBootCompleted = true;
171                 if (DEBUG) {
172                     Log.v(TAG, "BOOT_COMPLETED was already sent");
173                 }
174             }
175         }
176 
177         Log.v(TAG, "Starting SystemUI services for user " +
178                 Process.myUserHandle().getIdentifier() + ".");
179 
180         TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
181                 Trace.TRACE_TAG_APP);
182         log.traceBegin(metricsPrefix);
183 
184         final int N = services.length;
185         for (int i = 0; i < N; i++) {
186             String clsName = services[i];
187             if (DEBUG) Log.d(TAG, "loading: " + clsName);
188             log.traceBegin(metricsPrefix + clsName);
189             long ti = System.currentTimeMillis();
190             Class cls;
191             try {
192                 cls = Class.forName(clsName);
193                 Object o = cls.newInstance();
194                 if (o instanceof SystemUI.Injector) {
195                     o = ((SystemUI.Injector) o).apply(this);
196                 }
197                 mServices[i] = (SystemUI) o;
198             } catch(ClassNotFoundException ex){
199                 throw new RuntimeException(ex);
200             } catch (IllegalAccessException ex) {
201                 throw new RuntimeException(ex);
202             } catch (InstantiationException ex) {
203                 throw new RuntimeException(ex);
204             }
205 
206             mServices[i].mContext = this;
207             mServices[i].mComponents = mComponents;
208             if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
209             mServices[i].start();
210             log.traceEnd();
211 
212             // Warn if initialization of component takes too long
213             ti = System.currentTimeMillis() - ti;
214             if (ti > 1000) {
215                 Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
216             }
217             if (mBootCompleted) {
218                 mServices[i].onBootCompleted();
219             }
220         }
221         Dependency.get(InitController.class).executePostInitTasks();
222         log.traceEnd();
223         final Handler mainHandler = new Handler(Looper.getMainLooper());
224         Dependency.get(PluginManager.class).addPluginListener(
225                 new PluginListener<OverlayPlugin>() {
226                     private ArraySet<OverlayPlugin> mOverlays = new ArraySet<>();
227 
228                     @Override
229                     public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
230                         mainHandler.post(new Runnable() {
231                             @Override
232                             public void run() {
233                                 StatusBar statusBar = getComponent(StatusBar.class);
234                                 if (statusBar != null) {
235                                     plugin.setup(statusBar.getStatusBarWindow(),
236                                             statusBar.getNavigationBarView(), new Callback(plugin),
237                                             DozeParameters.getInstance(getBaseContext()));
238                                 }
239                             }
240                         });
241                     }
242 
243                     @Override
244                     public void onPluginDisconnected(OverlayPlugin plugin) {
245                         mainHandler.post(new Runnable() {
246                             @Override
247                             public void run() {
248                                 mOverlays.remove(plugin);
249                                 Dependency.get(StatusBarWindowController.class).setForcePluginOpen(
250                                         mOverlays.size() != 0);
251                             }
252                         });
253                     }
254 
255                     class Callback implements OverlayPlugin.Callback {
256                         private final OverlayPlugin mPlugin;
257 
258                         Callback(OverlayPlugin plugin) {
259                             mPlugin = plugin;
260                         }
261 
262                         @Override
263                         public void onHoldStatusBarOpenChange() {
264                             if (mPlugin.holdStatusBarOpen()) {
265                                 mOverlays.add(mPlugin);
266                             } else {
267                                 mOverlays.remove(mPlugin);
268                             }
269                             mainHandler.post(new Runnable() {
270                                 @Override
271                                 public void run() {
272                                     Dependency.get(StatusBarWindowController.class)
273                                             .setStateListener(b -> mOverlays.forEach(
274                                                     o -> o.setCollapseDesired(b)));
275                                     Dependency.get(StatusBarWindowController.class)
276                                             .setForcePluginOpen(mOverlays.size() != 0);
277                                 }
278                             });
279                         }
280                     }
281                 }, OverlayPlugin.class, true /* Allow multiple plugins */);
282 
283         mServicesStarted = true;
284     }
285 
286     @Override
onConfigurationChanged(Configuration newConfig)287     public void onConfigurationChanged(Configuration newConfig) {
288         if (mServicesStarted) {
289             SystemUIFactory
290                     .getInstance()
291                     .getRootComponent()
292                     .getConfigurationController()
293                     .onConfigurationChanged(newConfig);
294             int len = mServices.length;
295             for (int i = 0; i < len; i++) {
296                 if (mServices[i] != null) {
297                     mServices[i].onConfigurationChanged(newConfig);
298                 }
299             }
300         }
301     }
302 
303     @SuppressWarnings("unchecked")
getComponent(Class<T> interfaceType)304     public <T> T getComponent(Class<T> interfaceType) {
305         return (T) mComponents.get(interfaceType);
306     }
307 
getServices()308     public SystemUI[] getServices() {
309         return mServices;
310     }
311 
setContextAvailableCallback(ContextAvailableCallback callback)312     void setContextAvailableCallback(ContextAvailableCallback callback) {
313         mContextAvailableCallback = callback;
314     }
315 
316     interface ContextAvailableCallback {
onContextAvailable(Context context)317         void onContextAvailable(Context context);
318     }
319 }
320