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 androidx.appcompat.mms.CarrierConfigValuesLoader;
28 import androidx.appcompat.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         Context.RECEIVER_EXPORTED/*UNAUDITED*/);
137     }
138 
initMmsLib(final Context context, final BugleGservices bugleGservices, final CarrierConfigValuesLoader carrierConfigValuesLoader)139     private static void initMmsLib(final Context context, final BugleGservices bugleGservices,
140             final CarrierConfigValuesLoader carrierConfigValuesLoader) {
141         MmsManager.setApnSettingsLoader(new BugleApnSettingsLoader(context));
142         MmsManager.setCarrierConfigValuesLoader(carrierConfigValuesLoader);
143         MmsManager.setUserAgentInfoLoader(new BugleUserAgentInfoLoader(context));
144         MmsManager.setUseWakeLock(true);
145         // If Gservices is configured not to use mms api, force MmsManager to always use
146         // legacy mms sending logic
147         MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
148                 BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
149                 BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
150         bugleGservices.registerForChanges(new Runnable() {
151             @Override
152             public void run() {
153                 MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
154                         BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
155                         BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
156             }
157         });
158     }
159 
updateAppConfig(final Context context)160     public static void updateAppConfig(final Context context) {
161         // Make sure we set the correct state for the SMS/MMS receivers
162         SmsReceiver.updateSmsReceiveHandler(context);
163     }
164 
165     // Called from thread started in FactoryImpl.register() (i.e. not run in tests)
initializeAsync(final Factory factory)166     public void initializeAsync(final Factory factory) {
167         // Handle shared prefs upgrade & Load MMS Configuration
168         Trace.beginSection("app.initializeAsync");
169         maybeHandleSharedPrefsUpgrade(factory);
170         MmsConfig.load();
171         Trace.endSection();
172     }
173 
174     @Override
onLowMemory()175     public void onLowMemory() {
176         super.onLowMemory();
177 
178         if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
179             LogUtil.d(TAG, "BugleApplication.onLowMemory");
180         }
181         Factory.get().reclaimMemory();
182     }
183 
184     @Override
uncaughtException(final Thread thread, final Throwable ex)185     public void uncaughtException(final Thread thread, final Throwable ex) {
186         final boolean background = getMainLooper().getThread() != thread;
187         if (background) {
188             LogUtil.e(TAG, "Uncaught exception in background thread " + thread, ex);
189 
190             final Handler handler = new Handler(getMainLooper());
191             handler.post(new Runnable() {
192 
193                 @Override
194                 public void run() {
195                     sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
196                 }
197             });
198         } else {
199             sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
200         }
201     }
202 
maybeStartProfiling()203     private void maybeStartProfiling() {
204         // App startup profiling support. To use it:
205         //  adb shell setprop log.tag.BugleProfile DEBUG
206         //  #   Start the app, wait for a 30s, download trace file:
207         //  adb pull /data/data/com.android.messaging/cache/startup.trace /tmp
208         //  # Open trace file (using adt/tools/traceview)
209         if (android.util.Log.isLoggable(LogUtil.PROFILE_TAG, android.util.Log.DEBUG)) {
210             // Start method tracing with a big enough buffer and let it run for 30s.
211             // Note we use a logging tag as we don't want to wait for gservices to start up.
212             final File file = DebugUtils.getDebugFile("startup.trace", true);
213             if (file != null) {
214                 android.os.Debug.startMethodTracing(file.getAbsolutePath(), 160 * 1024 * 1024);
215                 new Handler(Looper.getMainLooper()).postDelayed(
216                        new Runnable() {
217                             @Override
218                             public void run() {
219                                 android.os.Debug.stopMethodTracing();
220                                 // Allow world to see trace file
221                                 DebugUtils.ensureReadable(file);
222                                 LogUtil.d(LogUtil.PROFILE_TAG, "Tracing complete - "
223                                      + file.getAbsolutePath());
224                             }
225                         }, 30000);
226             }
227         }
228     }
229 
maybeHandleSharedPrefsUpgrade(final Factory factory)230     private void maybeHandleSharedPrefsUpgrade(final Factory factory) {
231         final int existingVersion = factory.getApplicationPrefs().getInt(
232                 BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
233                 BuglePrefsKeys.SHARED_PREFERENCES_VERSION_DEFAULT);
234         final int targetVersion = Integer.parseInt(getString(R.string.pref_version));
235         if (targetVersion > existingVersion) {
236             LogUtil.i(LogUtil.BUGLE_TAG, "Upgrading shared prefs from " + existingVersion +
237                     " to " + targetVersion);
238             try {
239                 // Perform upgrade on application-wide prefs.
240                 factory.getApplicationPrefs().onUpgrade(existingVersion, targetVersion);
241                 // Perform upgrade on each subscription's prefs.
242                 PhoneUtils.forEachActiveSubscription(new PhoneUtils.SubscriptionRunnable() {
243                     @Override
244                     public void runForSubscription(final int subId) {
245                         factory.getSubscriptionPrefs(subId)
246                                 .onUpgrade(existingVersion, targetVersion);
247                     }
248                 });
249                 factory.getApplicationPrefs().putInt(BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
250                         targetVersion);
251             } catch (final Exception ex) {
252                 // Upgrade failed. Don't crash the app because we can always fall back to the
253                 // default settings.
254                 LogUtil.e(LogUtil.BUGLE_TAG, "Failed to upgrade shared prefs", ex);
255             }
256         } else if (targetVersion < existingVersion) {
257             // We don't care about downgrade since real user shouldn't encounter this, so log it
258             // and ignore any prefs migration.
259             LogUtil.e(LogUtil.BUGLE_TAG, "Shared prefs downgrade requested and ignored. " +
260                     "oldVersion = " + existingVersion + ", newVersion = " + targetVersion);
261         }
262     }
263 }
264