1 /*
2  * Copyright (C) 2015 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.messaging;
18 
19 import android.app.Application;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.res.Configuration;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.support.v7.mms.CarrierConfigValuesLoader;
28 import android.support.v7.mms.MmsManager;
29 import android.telephony.CarrierConfigManager;
30 
31 import com.android.messaging.datamodel.DataModel;
32 import com.android.messaging.receiver.SmsReceiver;
33 import com.android.messaging.sms.ApnDatabase;
34 import com.android.messaging.sms.BugleApnSettingsLoader;
35 import com.android.messaging.sms.BugleUserAgentInfoLoader;
36 import com.android.messaging.sms.MmsConfig;
37 import com.android.messaging.ui.ConversationDrawables;
38 import com.android.messaging.util.BugleGservices;
39 import com.android.messaging.util.BugleGservicesKeys;
40 import com.android.messaging.util.BuglePrefs;
41 import com.android.messaging.util.BuglePrefsKeys;
42 import com.android.messaging.util.DebugUtils;
43 import com.android.messaging.util.LogUtil;
44 import com.android.messaging.util.OsUtil;
45 import com.android.messaging.util.PhoneUtils;
46 import com.android.messaging.util.Trace;
47 import com.google.common.annotations.VisibleForTesting;
48 
49 import java.io.File;
50 import java.lang.Thread.UncaughtExceptionHandler;
51 
52 /**
53  * The application object
54  */
55 public class BugleApplication extends Application implements UncaughtExceptionHandler {
56     private static final String TAG = LogUtil.BUGLE_TAG;
57 
58     private UncaughtExceptionHandler sSystemUncaughtExceptionHandler;
59     private static boolean sRunningTests = false;
60 
61     @VisibleForTesting
setTestsRunning()62     protected static void setTestsRunning() {
63         sRunningTests = true;
64     }
65 
66     /**
67      * @return true if we're running unit tests.
68      */
isRunningTests()69     public static boolean isRunningTests() {
70         return sRunningTests;
71     }
72 
73     @Override
onCreate()74     public void onCreate() {
75         Trace.beginSection("app.onCreate");
76         super.onCreate();
77 
78         // Note onCreate is called in both test and real application environments
79         if (!sRunningTests) {
80             // Only create the factory if not running tests
81             FactoryImpl.register(getApplicationContext(), this);
82         } else {
83             LogUtil.e(TAG, "BugleApplication.onCreate: FactoryImpl.register skipped for test run");
84         }
85 
86         sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
87         Thread.setDefaultUncaughtExceptionHandler(this);
88         Trace.endSection();
89     }
90 
91     @Override
onConfigurationChanged(final Configuration newConfig)92     public void onConfigurationChanged(final Configuration newConfig) {
93         super.onConfigurationChanged(newConfig);
94 
95         // Update conversation drawables when changing writing systems
96         // (Right-To-Left / Left-To-Right)
97         ConversationDrawables.get().updateDrawables();
98     }
99 
100     // Called by the "real" factory from FactoryImpl.register() (i.e. not run in tests)
initializeSync(final Factory factory)101     public void initializeSync(final Factory factory) {
102         Trace.beginSection("app.initializeSync");
103         final Context context = factory.getApplicationContext();
104         final BugleGservices bugleGservices = factory.getBugleGservices();
105         final BuglePrefs buglePrefs = factory.getApplicationPrefs();
106         final DataModel dataModel = factory.getDataModel();
107         final CarrierConfigValuesLoader carrierConfigValuesLoader =
108                 factory.getCarrierConfigValuesLoader();
109 
110         maybeStartProfiling();
111 
112         BugleApplication.updateAppConfig(context);
113 
114         // Initialize MMS lib
115         initMmsLib(context, bugleGservices, carrierConfigValuesLoader);
116         // Initialize APN database
117         ApnDatabase.initializeAppContext(context);
118         // Fixup messages in flight if we crashed and send any pending
119         dataModel.onApplicationCreated();
120         // Register carrier config change receiver
121         if (OsUtil.isAtLeastM()) {
122             registerCarrierConfigChangeReceiver(context);
123         }
124 
125         Trace.endSection();
126     }
127 
registerCarrierConfigChangeReceiver(final Context context)128     private static void registerCarrierConfigChangeReceiver(final Context context) {
129         context.registerReceiver(new BroadcastReceiver() {
130             @Override
131             public void onReceive(Context context, Intent intent) {
132                 LogUtil.i(TAG, "Carrier config changed. Reloading MMS config.");
133                 MmsConfig.loadAsync();
134             }
135         }, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
136     }
137 
initMmsLib(final Context context, final BugleGservices bugleGservices, final CarrierConfigValuesLoader carrierConfigValuesLoader)138     private static void initMmsLib(final Context context, final BugleGservices bugleGservices,
139             final CarrierConfigValuesLoader carrierConfigValuesLoader) {
140         MmsManager.setApnSettingsLoader(new BugleApnSettingsLoader(context));
141         MmsManager.setCarrierConfigValuesLoader(carrierConfigValuesLoader);
142         MmsManager.setUserAgentInfoLoader(new BugleUserAgentInfoLoader(context));
143         MmsManager.setUseWakeLock(true);
144         // If Gservices is configured not to use mms api, force MmsManager to always use
145         // legacy mms sending logic
146         MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
147                 BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
148                 BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
149         bugleGservices.registerForChanges(new Runnable() {
150             @Override
151             public void run() {
152                 MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
153                         BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
154                         BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
155             }
156         });
157     }
158 
updateAppConfig(final Context context)159     public static void updateAppConfig(final Context context) {
160         // Make sure we set the correct state for the SMS/MMS receivers
161         SmsReceiver.updateSmsReceiveHandler(context);
162     }
163 
164     // Called from thread started in FactoryImpl.register() (i.e. not run in tests)
initializeAsync(final Factory factory)165     public void initializeAsync(final Factory factory) {
166         // Handle shared prefs upgrade & Load MMS Configuration
167         Trace.beginSection("app.initializeAsync");
168         maybeHandleSharedPrefsUpgrade(factory);
169         MmsConfig.load();
170         Trace.endSection();
171     }
172 
173     @Override
onLowMemory()174     public void onLowMemory() {
175         super.onLowMemory();
176 
177         if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
178             LogUtil.d(TAG, "BugleApplication.onLowMemory");
179         }
180         Factory.get().reclaimMemory();
181     }
182 
183     @Override
uncaughtException(final Thread thread, final Throwable ex)184     public void uncaughtException(final Thread thread, final Throwable ex) {
185         final boolean background = getMainLooper().getThread() != thread;
186         if (background) {
187             LogUtil.e(TAG, "Uncaught exception in background thread " + thread, ex);
188 
189             final Handler handler = new Handler(getMainLooper());
190             handler.post(new Runnable() {
191 
192                 @Override
193                 public void run() {
194                     sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
195                 }
196             });
197         } else {
198             sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
199         }
200     }
201 
maybeStartProfiling()202     private void maybeStartProfiling() {
203         // App startup profiling support. To use it:
204         //  adb shell setprop log.tag.BugleProfile DEBUG
205         //  #   Start the app, wait for a 30s, download trace file:
206         //  adb pull /data/data/com.android.messaging/cache/startup.trace /tmp
207         //  # Open trace file (using adt/tools/traceview)
208         if (android.util.Log.isLoggable(LogUtil.PROFILE_TAG, android.util.Log.DEBUG)) {
209             // Start method tracing with a big enough buffer and let it run for 30s.
210             // Note we use a logging tag as we don't want to wait for gservices to start up.
211             final File file = DebugUtils.getDebugFile("startup.trace", true);
212             if (file != null) {
213                 android.os.Debug.startMethodTracing(file.getAbsolutePath(), 160 * 1024 * 1024);
214                 new Handler(Looper.getMainLooper()).postDelayed(
215                        new Runnable() {
216                             @Override
217                             public void run() {
218                                 android.os.Debug.stopMethodTracing();
219                                 // Allow world to see trace file
220                                 DebugUtils.ensureReadable(file);
221                                 LogUtil.d(LogUtil.PROFILE_TAG, "Tracing complete - "
222                                      + file.getAbsolutePath());
223                             }
224                         }, 30000);
225             }
226         }
227     }
228 
maybeHandleSharedPrefsUpgrade(final Factory factory)229     private void maybeHandleSharedPrefsUpgrade(final Factory factory) {
230         final int existingVersion = factory.getApplicationPrefs().getInt(
231                 BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
232                 BuglePrefsKeys.SHARED_PREFERENCES_VERSION_DEFAULT);
233         final int targetVersion = Integer.parseInt(getString(R.string.pref_version));
234         if (targetVersion > existingVersion) {
235             LogUtil.i(LogUtil.BUGLE_TAG, "Upgrading shared prefs from " + existingVersion +
236                     " to " + targetVersion);
237             try {
238                 // Perform upgrade on application-wide prefs.
239                 factory.getApplicationPrefs().onUpgrade(existingVersion, targetVersion);
240                 // Perform upgrade on each subscription's prefs.
241                 PhoneUtils.forEachActiveSubscription(new PhoneUtils.SubscriptionRunnable() {
242                     @Override
243                     public void runForSubscription(final int subId) {
244                         factory.getSubscriptionPrefs(subId)
245                                 .onUpgrade(existingVersion, targetVersion);
246                     }
247                 });
248                 factory.getApplicationPrefs().putInt(BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
249                         targetVersion);
250             } catch (final Exception ex) {
251                 // Upgrade failed. Don't crash the app because we can always fall back to the
252                 // default settings.
253                 LogUtil.e(LogUtil.BUGLE_TAG, "Failed to upgrade shared prefs", ex);
254             }
255         } else if (targetVersion < existingVersion) {
256             // We don't care about downgrade since real user shouldn't encounter this, so log it
257             // and ignore any prefs migration.
258             LogUtil.e(LogUtil.BUGLE_TAG, "Shared prefs downgrade requested and ignored. " +
259                     "oldVersion = " + existingVersion + ", newVersion = " + targetVersion);
260         }
261     }
262 }
263