1/*******************************************************************************
2 *      Copyright (C) 2012 Google Inc.
3 *      Licensed to The Android Open Source Project.
4 *
5 *      Licensed under the Apache License, Version 2.0 (the "License");
6 *      you may not use this file except in compliance with the License.
7 *      You may obtain a copy of the License at
8 *
9 *           http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *      Unless required by applicable law or agreed to in writing, software
12 *      distributed under the License is distributed on an "AS IS" BASIS,
13 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *      See the License for the specific language governing permissions and
15 *      limitations under the License.
16 *******************************************************************************/
17
18package com.android.mail.ui;
19
20import android.app.Dialog;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.Configuration;
24import android.nfc.NdefMessage;
25import android.nfc.NdefRecord;
26import android.nfc.NfcAdapter;
27import android.nfc.NfcEvent;
28import android.os.Bundle;
29import android.view.DragEvent;
30import android.view.KeyEvent;
31import android.view.Menu;
32import android.view.MenuItem;
33import android.view.MotionEvent;
34import android.view.View;
35import android.view.accessibility.AccessibilityManager;
36
37import com.android.mail.compose.ComposeActivity;
38import com.android.mail.providers.Folder;
39import com.android.mail.utils.StorageLowState;
40import com.android.mail.utils.Utils;
41
42import java.io.UnsupportedEncodingException;
43import java.net.URLEncoder;
44
45/**
46 * This is the root activity container that holds the left navigation fragment
47 * (usually a list of folders), and the main content fragment (either a
48 * conversation list or a conversation view).
49 */
50public class MailActivity extends AbstractMailActivity implements ControllableActivity {
51    /**
52     * The activity controller to which we delegate most Activity lifecycle events.
53     */
54    private ActivityController mController;
55
56    private ViewMode mViewMode;
57
58    private ToastBarOperation mPendingToastOp;
59    private boolean mAccessibilityEnabled;
60    private AccessibilityManager mAccessibilityManager;
61
62    protected ConversationListHelper mConversationListHelper;
63
64    /**
65     * The account name currently in use. Used to construct the NFC mailto: message. This needs
66     * to be static since the {@link ComposeActivity} needs to statically change the account name
67     * and have the NFC message changed accordingly.
68     */
69    protected static String sAccountName = null;
70
71    /**
72     * Create an NFC message (in the NDEF: Nfc Data Exchange Format) to instruct the recepient to
73     * send an email to the current account.
74     */
75    private static class NdefMessageMaker implements NfcAdapter.CreateNdefMessageCallback {
76        @Override
77        public NdefMessage createNdefMessage(NfcEvent event) {
78            if (sAccountName == null) {
79                return null;
80            }
81            return getMailtoNdef(sAccountName);
82        }
83
84        /**
85         * Returns an NDEF message with a single mailto URI record
86         * for the given email address.
87         */
88        private static NdefMessage getMailtoNdef(String account) {
89            byte[] accountBytes;
90            try {
91                accountBytes = URLEncoder.encode(account, "UTF-8").getBytes("UTF-8");
92            } catch (UnsupportedEncodingException e) {
93                accountBytes = account.getBytes();
94            }
95            byte prefix = 0x06; // mailto:
96            byte[] recordBytes = new byte[accountBytes.length + 1];
97            recordBytes[0] = prefix;
98            System.arraycopy(accountBytes, 0, recordBytes, 1, accountBytes.length);
99            NdefRecord mailto = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI,
100                    new byte[0], recordBytes);
101            return new NdefMessage(new NdefRecord[] { mailto });
102        }
103    }
104
105    private final NdefMessageMaker mNdefHandler = new NdefMessageMaker();
106
107    public MailActivity() {
108        super();
109        mConversationListHelper = new ConversationListHelper();
110    }
111
112    @Override
113    public boolean dispatchTouchEvent(MotionEvent ev) {
114        mController.onTouchEvent(ev);
115        return super.dispatchTouchEvent(ev);
116    }
117
118    /**
119     * Default implementation returns a null view mode.
120     */
121    @Override
122    public ViewMode getViewMode() {
123        return mViewMode;
124    }
125
126    @Override
127    public void onActivityResult(int requestCode, int resultCode, Intent data) {
128        mController.onActivityResult(requestCode, resultCode, data);
129    }
130
131    @Override
132    public void onBackPressed() {
133        if (!mController.onBackPressed()) {
134            super.onBackPressed();
135        }
136    }
137
138    @Override
139    public void onCreate(Bundle savedState) {
140        super.onCreate(savedState);
141
142        mViewMode = new ViewMode();
143        final boolean tabletUi = Utils.useTabletUI(this.getResources());
144        mController = ControllerFactory.forActivity(this, mViewMode, tabletUi);
145        mController.onCreate(savedState);
146
147        mAccessibilityManager =
148                (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
149        mAccessibilityEnabled = mAccessibilityManager.isEnabled();
150        final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
151        if (nfcAdapter != null) {
152            nfcAdapter.setNdefPushMessageCallback(mNdefHandler, this);
153        }
154    }
155
156    @Override
157    protected void onPostCreate(Bundle savedInstanceState) {
158        super.onPostCreate(savedInstanceState);
159
160        mController.onPostCreate(savedInstanceState);
161    }
162
163    @Override
164    public void onConfigurationChanged(Configuration newConfig) {
165        super.onConfigurationChanged(newConfig);
166        mController.onConfigurationChanged(newConfig);
167    }
168
169    @Override
170    protected void onRestart() {
171        super.onRestart();
172        mController.onRestart();
173    }
174
175    /**
176     * Constructs and sets the default NFC message. This message instructs the receiver to send
177     * email to the account provided as the argument. This message is to be shared with
178     * "zero-clicks" using NFC. The message will be available as long as the current activity is in
179     * the foreground.
180     *
181     * @param account The email address to send mail to.
182     */
183    public static void setNfcMessage(String account) {
184        sAccountName = account;
185    }
186
187    @Override
188    protected void onRestoreInstanceState(Bundle savedInstanceState) {
189        super.onRestoreInstanceState(savedInstanceState);
190        mController.onRestoreInstanceState(savedInstanceState);
191    }
192
193    @Override
194    public Dialog onCreateDialog(int id, Bundle bundle) {
195        final Dialog dialog = mController.onCreateDialog(id, bundle);
196        return dialog == null ? super.onCreateDialog(id, bundle) : dialog;
197    }
198
199    @Override
200    public boolean onCreateOptionsMenu(Menu menu) {
201        return mController.onCreateOptionsMenu(menu) || super.onCreateOptionsMenu(menu);
202    }
203
204    @Override
205    public boolean onKeyDown(int keyCode, KeyEvent event) {
206        return mController.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
207    }
208
209    @Override
210    public boolean onOptionsItemSelected(MenuItem item) {
211        return mController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
212    }
213
214    @Override
215    public void onPause() {
216        super.onPause();
217        mController.onPause();
218    }
219
220    @Override
221    public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
222        super.onPrepareDialog(id, dialog, bundle);
223        mController.onPrepareDialog(id, dialog, bundle);
224    }
225
226    @Override
227    public boolean onPrepareOptionsMenu(Menu menu) {
228        mController.onPrepareOptionsMenu(menu);
229        return super.onPrepareOptionsMenu(menu);
230    }
231
232    @Override
233    public void onResume() {
234        super.onResume();
235        mController.onResume();
236        final boolean enabled = mAccessibilityManager.isEnabled();
237        if (enabled != mAccessibilityEnabled) {
238            onAccessibilityStateChanged(enabled);
239        }
240        // App has resumed, re-check the top-level storage situation.
241        StorageLowState.checkStorageLowMode(this);
242    }
243
244    @Override
245    public void onSaveInstanceState(Bundle outState) {
246        super.onSaveInstanceState(outState);
247        mController.onSaveInstanceState(outState);
248    }
249
250    @Override
251    protected void onStart() {
252        super.onStart();
253        mController.onStart();
254    }
255
256    @Override
257    public boolean onSearchRequested() {
258        mController.startSearch();
259        return true;
260    }
261
262    @Override
263    public void onStop() {
264        super.onStop();
265        mController.onStop();
266    }
267
268    @Override
269    protected void onDestroy() {
270        super.onDestroy();
271        mController.onDestroy();
272    }
273
274    @Override
275    public void onWindowFocusChanged(boolean hasFocus) {
276        super.onWindowFocusChanged(hasFocus);
277        mController.onWindowFocusChanged(hasFocus);
278    }
279
280    @Override
281    public String toString() {
282        final StringBuilder sb = new StringBuilder(super.toString());
283        sb.append("{ViewMode=");
284        sb.append(mViewMode);
285        sb.append(" controller=");
286        sb.append(mController);
287        sb.append("}");
288        return sb.toString();
289    }
290
291    @Override
292    public ConversationListCallbacks getListHandler() {
293        return mController;
294    }
295
296    @Override
297    public FolderChangeListener getFolderChangeListener() {
298        return mController;
299    }
300
301    @Override
302    public FolderSelector getFolderSelector() {
303        return mController;
304    }
305
306    @Override
307    public FolderController getFolderController() {
308        return mController;
309    }
310
311    @Override
312    public ConversationSelectionSet getSelectedSet() {
313        return mController.getSelectedSet();
314    }
315
316    @Override
317    public boolean supportsDrag(DragEvent event, Folder folder) {
318        return mController.supportsDrag(event, folder);
319    }
320
321    @Override
322    public void handleDrop(DragEvent event, Folder folder) {
323        mController.handleDrop(event, folder);
324    }
325
326    @Override
327    public void onUndoAvailable(ToastBarOperation undoOp) {
328        mController.onUndoAvailable(undoOp);
329    }
330
331    @Override
332    public Folder getHierarchyFolder() {
333        return mController.getHierarchyFolder();
334    }
335
336    @Override
337    public ConversationUpdater getConversationUpdater() {
338        return mController;
339    }
340
341    @Override
342    public ErrorListener getErrorListener() {
343        return mController;
344    }
345
346    @Override
347    public void setPendingToastOperation(ToastBarOperation op) {
348        mPendingToastOp = op;
349    }
350
351    @Override
352    public ToastBarOperation getPendingToastOperation() {
353        return mPendingToastOp;
354    }
355
356    /**
357     * Sole purpose of this method is to stop clicks where we don't want them in
358     * the action bar. This can be hooked as a listener to view with
359     * android:onClick.
360     *
361     * @param v
362     */
363    public void doNothingClickHandler(View v) {
364        // Do nothing.
365    }
366
367    @Override
368    public void onAnimationEnd(AnimatedAdapter animatedAdapter) {
369        mController.onAnimationEnd(animatedAdapter);
370    }
371
372    @Override
373    public AccountController getAccountController() {
374        return mController;
375    }
376
377    @Override
378    public RecentFolderController getRecentFolderController() {
379        return mController;
380    }
381
382    @Override
383    public UpOrBackController getUpOrBackController() {
384        return mController;
385    }
386
387    @Override
388    public void onFooterViewErrorActionClick(Folder folder, int errorStatus) {
389        mController.onFooterViewErrorActionClick(folder, errorStatus);
390    }
391
392    @Override
393    public void onFooterViewLoadMoreClick(Folder folder) {
394        mController.onFooterViewLoadMoreClick(folder);
395    }
396
397    @Override
398    public void startDragMode() {
399        mController.startDragMode();
400    }
401
402    @Override
403    public void stopDragMode() {
404        mController.stopDragMode();
405    }
406
407    @Override
408    public boolean isAccessibilityEnabled() {
409        return mAccessibilityEnabled;
410    }
411
412    public void onAccessibilityStateChanged(boolean enabled) {
413        mAccessibilityEnabled = enabled;
414        mController.onAccessibilityStateChanged();
415    }
416
417    @Override
418    public final ConversationListHelper getConversationListHelper() {
419        return mConversationListHelper;
420    }
421
422    @Override
423    public FragmentLauncher getFragmentLauncher() {
424        return mController;
425    }
426}
427