1/*
2 * Copyright (C) 2008 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.email2.ui;
18
19import android.content.ComponentName;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.UriMatcher;
24import android.content.pm.PackageManager;
25import android.database.Cursor;
26import android.net.Uri;
27import android.os.Bundle;
28
29import com.android.email.NotificationController;
30import com.android.email.Preferences;
31import com.android.email.R;
32import com.android.email.provider.EmailProvider;
33import com.android.email.service.AttachmentDownloadService;
34import com.android.email.service.EmailServiceUtils;
35import com.android.emailcommon.Logging;
36import com.android.emailcommon.TempDirectory;
37import com.android.emailcommon.provider.Account;
38import com.android.emailcommon.provider.EmailContent;
39import com.android.emailcommon.provider.Mailbox;
40import com.android.emailcommon.service.EmailServiceProxy;
41import com.android.emailcommon.utility.EmailAsyncTask;
42import com.android.emailcommon.utility.IntentUtilities;
43import com.android.emailcommon.utility.Utility;
44import com.android.mail.providers.Folder;
45import com.android.mail.providers.UIProvider;
46import com.android.mail.utils.LogTag;
47import com.android.mail.utils.LogUtils;
48import com.android.mail.utils.Utils;
49
50public class MailActivityEmail extends com.android.mail.ui.MailActivity {
51    /**
52     * If this is enabled there will be additional logging information sent to
53     * LogUtils.d, including protocol dumps.
54     *
55     * This should only be used for logs that are useful for debbuging user problems,
56     * not for internal/development logs.
57     *
58     * This can be enabled by typing "debug" in the AccountFolderList activity.
59     * Changing the value to 'true' here will likely have no effect at all!
60     *
61     * TODO: rename this to sUserDebug, and rename LOGD below to DEBUG.
62     */
63    public static boolean DEBUG;
64
65    public static final String LOG_TAG = LogTag.getLogTag();
66
67    // Exchange debugging flags (passed to Exchange, when available, via EmailServiceProxy)
68    public static boolean DEBUG_EXCHANGE;
69    public static boolean DEBUG_VERBOSE;
70    public static boolean DEBUG_FILE;
71
72    /**
73     * If true, inhibit hardware graphics acceleration in UI (for a/b testing)
74     */
75    public static boolean sDebugInhibitGraphicsAcceleration = false;
76
77    /**
78     * This is used to force stacked UI to return to the "welcome" screen any time we change
79     * the accounts list (e.g. deleting accounts in the Account Manager preferences.)
80     */
81    private static boolean sAccountsChangedNotification = false;
82
83    private static String sMessageDecodeErrorString;
84
85    private static Thread sUiThread;
86
87    private static final int MATCH_LEGACY_SHORTCUT_INTENT = 1;
88    /**
89     * A matcher for data URI's that specify conversation list info.
90     */
91    private static final UriMatcher sUrlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
92    static {
93        sUrlMatcher.addURI(
94                EmailProvider.LEGACY_AUTHORITY, "view/mailbox", MATCH_LEGACY_SHORTCUT_INTENT);
95    }
96
97
98    /**
99     * Asynchronous version of {@link #setServicesEnabledSync(Context)}.  Use when calling from
100     * UI thread (or lifecycle entry points.)
101     *
102     * @param context
103     */
104    public static void setServicesEnabledAsync(final Context context) {
105        EmailAsyncTask.runAsyncParallel(new Runnable() {
106            @Override
107            public void run() {
108                setServicesEnabledSync(context);
109            }
110        });
111    }
112
113    /**
114     * Called throughout the application when the number of accounts has changed. This method
115     * enables or disables the Compose activity, the boot receiver and the service based on
116     * whether any accounts are configured.
117     *
118     * Blocking call - do not call from UI/lifecycle threads.
119     *
120     * @param context
121     * @return true if there are any accounts configured.
122     */
123    public static boolean setServicesEnabledSync(Context context) {
124        // Make sure we're initialized
125        EmailContent.init(context);
126        Cursor c = null;
127        try {
128            c = context.getContentResolver().query(
129                    Account.CONTENT_URI,
130                    Account.ID_PROJECTION,
131                    null, null, null);
132            boolean enable = c.getCount() > 0;
133            setServicesEnabled(context, enable);
134            return enable;
135        } finally {
136            if (c != null) {
137                c.close();
138            }
139        }
140    }
141
142    private static void setServicesEnabled(Context context, boolean enabled) {
143        PackageManager pm = context.getPackageManager();
144        pm.setComponentEnabledSetting(
145                new ComponentName(context, AttachmentDownloadService.class),
146                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
147                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
148                PackageManager.DONT_KILL_APP);
149
150        // Start/stop the various services depending on whether there are any accounts
151        startOrStopService(enabled, context, new Intent(context, AttachmentDownloadService.class));
152        NotificationController.getInstance(context).watchForMessages();
153    }
154
155    /**
156     * Starts or stops the service as necessary.
157     * @param enabled If {@code true}, the service will be started. Otherwise, it will be stopped.
158     * @param context The context to manage the service with.
159     * @param intent The intent of the service to be managed.
160     */
161    private static void startOrStopService(boolean enabled, Context context, Intent intent) {
162        if (enabled) {
163            context.startService(intent);
164        } else {
165            context.stopService(intent);
166        }
167    }
168
169    @Override
170    public void onCreate(Bundle bundle) {
171        final Intent intent = getIntent();
172        final Uri data = intent != null ? intent.getData() : null;
173        if (data != null) {
174            final int match = sUrlMatcher.match(data);
175            switch (match) {
176                case MATCH_LEGACY_SHORTCUT_INTENT: {
177                    final long mailboxId = IntentUtilities.getMailboxIdFromIntent(intent);
178                    final Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId);
179                    if (mailbox == null) {
180                        LogUtils.e(LOG_TAG, "unable to restore mailbox");
181                        break;
182                    }
183
184                    final Intent viewIntent = getViewIntent(mailbox.mAccountKey, mailboxId);
185                    if (viewIntent != null) {
186                        setIntent(viewIntent);
187                    }
188                    break;
189                }
190            }
191        }
192
193        super.onCreate(bundle);
194        sUiThread = Thread.currentThread();
195        Preferences prefs = Preferences.getPreferences(this);
196        DEBUG = prefs.getEnableDebugLogging();
197        sDebugInhibitGraphicsAcceleration = prefs.getInhibitGraphicsAcceleration();
198        enableStrictMode(prefs.getEnableStrictMode());
199        TempDirectory.setTempDirectory(this);
200
201        // Enable logging in the EAS service, so it starts up as early as possible.
202        updateLoggingFlags(this);
203
204        // Get a helper string used deep inside message decoders (which don't have context)
205        sMessageDecodeErrorString = getString(R.string.message_decode_error);
206
207        // Make sure all required services are running when the app is started (can prevent
208        // issues after an adb sync/install)
209        setServicesEnabledAsync(this);
210    }
211
212    /**
213     * Load enabled debug flags from the preferences and update the EAS debug flag.
214     */
215    public static void updateLoggingFlags(Context context) {
216        Preferences prefs = Preferences.getPreferences(context);
217        int debugLogging = prefs.getEnableDebugLogging() ? EmailServiceProxy.DEBUG_BIT : 0;
218        int verboseLogging =
219            prefs.getEnableExchangeLogging() ? EmailServiceProxy.DEBUG_VERBOSE_BIT : 0;
220        int fileLogging =
221            prefs.getEnableExchangeFileLogging() ? EmailServiceProxy.DEBUG_FILE_BIT : 0;
222        int enableStrictMode =
223            prefs.getEnableStrictMode() ? EmailServiceProxy.DEBUG_ENABLE_STRICT_MODE : 0;
224        int debugBits = debugLogging | verboseLogging | fileLogging | enableStrictMode;
225        EmailServiceUtils.setRemoteServicesLogging(context, debugBits);
226     }
227
228    /**
229     * Internal, utility method for logging.
230     * The calls to log() must be guarded with "if (Email.LOGD)" for performance reasons.
231     */
232    public static void log(String message) {
233        LogUtils.d(Logging.LOG_TAG, message);
234    }
235
236    /**
237     * Called by the accounts reconciler to notify that accounts have changed, or by  "Welcome"
238     * to clear the flag.
239     * @param setFlag true to set the notification flag, false to clear it
240     */
241    public static synchronized void setNotifyUiAccountsChanged(boolean setFlag) {
242        sAccountsChangedNotification = setFlag;
243    }
244
245    /**
246     * Called from activity onResume() functions to check for an accounts-changed condition, at
247     * which point they should finish() and jump to the Welcome activity.
248     */
249    public static synchronized boolean getNotifyUiAccountsChanged() {
250        return sAccountsChangedNotification;
251    }
252
253    public static void warnIfUiThread() {
254        if (Thread.currentThread().equals(sUiThread)) {
255            LogUtils.w(Logging.LOG_TAG, "Method called on the UI thread",
256                    new Exception("STACK TRACE"));
257        }
258    }
259
260    /**
261     * Retrieve a simple string that can be used when message decoders encounter bad data.
262     * This is provided here because the protocol decoders typically don't have mContext.
263     */
264    public static String getMessageDecodeErrorString() {
265        return sMessageDecodeErrorString != null ? sMessageDecodeErrorString : "";
266    }
267
268    public static void enableStrictMode(boolean enabled) {
269        Utility.enableStrictMode(enabled);
270    }
271
272    private Intent getViewIntent(long accountId, long mailboxId) {
273        final ContentResolver contentResolver = getContentResolver();
274
275        final Cursor accountCursor = contentResolver.query(
276                EmailProvider.uiUri("uiaccount", accountId),
277                UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES,
278                null, null, null);
279
280        if (accountCursor == null) {
281            LogUtils.e(LOG_TAG, "Null account cursor for mAccountId %d", accountId);
282            return null;
283        }
284
285        com.android.mail.providers.Account account = null;
286        try {
287            if (accountCursor.moveToFirst()) {
288                account = new com.android.mail.providers.Account(accountCursor);
289            }
290        } finally {
291            accountCursor.close();
292        }
293
294
295        final Cursor folderCursor = contentResolver.query(
296                EmailProvider.uiUri("uifolder", mailboxId),
297                UIProvider.FOLDERS_PROJECTION, null, null, null);
298
299        if (folderCursor == null) {
300            LogUtils.e(LOG_TAG, "Null folder cursor for account %d, mailbox %d",
301                    accountId, mailboxId);
302            return null;
303        }
304
305        Folder folder = null;
306        try {
307            if (folderCursor.moveToFirst()) {
308                folder = new Folder(folderCursor);
309            } else {
310                LogUtils.e(LOG_TAG, "Empty folder cursor for account %d, mailbox %d",
311                        accountId, mailboxId);
312                return null;
313            }
314        } finally {
315            folderCursor.close();
316        }
317
318        return Utils.createViewFolderIntent(this, folder.folderUri.fullUri, account);
319    }
320}
321