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
17package com.android.messaging;
18
19import android.app.Application;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.res.Configuration;
25import android.os.Handler;
26import android.os.Looper;
27import android.support.v7.mms.CarrierConfigValuesLoader;
28import android.support.v7.mms.MmsManager;
29import android.telephony.CarrierConfigManager;
30
31import com.android.messaging.datamodel.DataModel;
32import com.android.messaging.receiver.SmsReceiver;
33import com.android.messaging.sms.ApnDatabase;
34import com.android.messaging.sms.BugleApnSettingsLoader;
35import com.android.messaging.sms.BugleUserAgentInfoLoader;
36import com.android.messaging.sms.MmsConfig;
37import com.android.messaging.ui.ConversationDrawables;
38import com.android.messaging.util.BugleGservices;
39import com.android.messaging.util.BugleGservicesKeys;
40import com.android.messaging.util.BuglePrefs;
41import com.android.messaging.util.BuglePrefsKeys;
42import com.android.messaging.util.DebugUtils;
43import com.android.messaging.util.LogUtil;
44import com.android.messaging.util.OsUtil;
45import com.android.messaging.util.PhoneUtils;
46import com.android.messaging.util.Trace;
47import com.google.common.annotations.VisibleForTesting;
48
49import java.io.File;
50import java.lang.Thread.UncaughtExceptionHandler;
51
52/**
53 * The application object
54 */
55public 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
62    protected static void setTestsRunning() {
63        sRunningTests = true;
64    }
65
66    /**
67     * @return true if we're running unit tests.
68     */
69    public static boolean isRunningTests() {
70        return sRunningTests;
71    }
72
73    @Override
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
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)
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
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
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
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)
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
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
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
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
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