UIControllerBase.java revision 2866284a6d4ee4b368fcaf412c540b21d457e065
1/*
2 * Copyright (C) 2011 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.email.activity;
18
19import com.android.email.Email;
20import com.android.email.R;
21import com.android.email.RefreshManager;
22import com.android.email.activity.setup.AccountSettings;
23import com.android.emailcommon.Logging;
24import com.android.emailcommon.provider.EmailContent.Account;
25import com.android.emailcommon.utility.EmailAsyncTask;
26
27import android.app.Fragment;
28import android.app.FragmentManager;
29import android.app.FragmentTransaction;
30import android.os.Bundle;
31import android.util.Log;
32import android.view.Menu;
33import android.view.MenuInflater;
34import android.view.MenuItem;
35
36import java.util.ArrayList;
37
38/**
39 * Base class for the UI controller.
40 *
41 * Note: Always use {@link #commitFragmentTransaction} and {@link #popBackStack} to operate fragment
42 * transactions.
43 * (Currently we use synchronous transactions only, but we may want to switch back to asynchronous
44 * later.)
45 */
46abstract class UIControllerBase {
47    protected static final String BUNDLE_KEY_ACCOUNT_ID = "UIController.state.account_id";
48    protected static final String BUNDLE_KEY_MAILBOX_ID = "UIController.state.mailbox_id";
49    protected static final String BUNDLE_KEY_MESSAGE_ID = "UIController.state.message_id";
50
51    /** No account selected */
52    static final long NO_ACCOUNT = Account.PSEUDO_ACCOUNT_ID_NONE;
53    /** No mailbox selected */
54    static final long NO_MAILBOX = -1;
55    /** No message selected */
56    static final long NO_MESSAGE = -1;
57
58    /** The owner activity */
59    final EmailActivity mActivity;
60
61    final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
62
63    final RefreshManager mRefreshManager;
64
65    /**
66     * List of fragments that are restored by the framework while the activity is being re-created
67     * for configuration changes (e.g. screen rotation).  We'll install them later when the activity
68     * is created in {@link #installRestoredFragments()}.
69     */
70    private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>();
71
72    /**
73     * Whether fragment installation should be hold.
74     * We hold installing fragments until {@link #installRestoredFragments()} is called.
75     */
76    private boolean mHoldFragmentInstallation = true;
77
78    private final RefreshManager.Listener mRefreshListener
79            = new RefreshManager.Listener() {
80        @Override
81        public void onMessagingError(final long accountId, long mailboxId, final String message) {
82            updateRefreshProgress();
83        }
84
85        @Override
86        public void onRefreshStatusChanged(long accountId, long mailboxId) {
87            updateRefreshProgress();
88        }
89    };
90
91    public UIControllerBase(EmailActivity activity) {
92        mActivity = activity;
93        mRefreshManager = RefreshManager.getInstance(mActivity);
94    }
95
96    /** @return the layout ID for the activity. */
97    public abstract int getLayoutId();
98
99    /**
100     * @return true if the UI controller currently can install fragments.
101     */
102    boolean isFragmentInstallable() {
103        return !mHoldFragmentInstallation;
104    }
105
106    /**
107     * Must be called just after the activity sets up the content view.  Used to initialize views.
108     *
109     * (Due to the complexity regarding class/activity initialization order, we can't do this in
110     * the constructor.)
111     */
112    public void onActivityViewReady() {
113        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
114            Log.d(Logging.LOG_TAG, this + " onActivityViewReady");
115        }
116    }
117
118    /**
119     * Called at the end of {@link EmailActivity#onCreate}.
120     */
121    public void onActivityCreated() {
122        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
123            Log.d(Logging.LOG_TAG, this + " onActivityCreated");
124        }
125        mRefreshManager.registerListener(mRefreshListener);
126    }
127
128    /**
129     * Handles the {@link android.app.Activity#onStart} callback.
130     */
131    public void onActivityStart() {
132        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
133            Log.d(Logging.LOG_TAG, this + " onActivityStart");
134        }
135    }
136
137    /**
138     * Handles the {@link android.app.Activity#onResume} callback.
139     */
140    public void onActivityResume() {
141        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
142            Log.d(Logging.LOG_TAG, this + " onActivityResume");
143        }
144    }
145
146    /**
147     * Handles the {@link android.app.Activity#onPause} callback.
148     */
149    public void onActivityPause() {
150        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
151            Log.d(Logging.LOG_TAG, this + " onActivityPause");
152        }
153    }
154
155    /**
156     * Handles the {@link android.app.Activity#onStop} callback.
157     */
158    public void onActivityStop() {
159        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
160            Log.d(Logging.LOG_TAG, this + " onActivityStop");
161        }
162    }
163
164    /**
165     * Handles the {@link android.app.Activity#onDestroy} callback.
166     */
167    public void onActivityDestroy() {
168        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
169            Log.d(Logging.LOG_TAG, this + " onActivityDestroy");
170        }
171        mHoldFragmentInstallation = true; // No more fragment installation.
172        mRefreshManager.unregisterListener(mRefreshListener);
173        mTaskTracker.cancellAllInterrupt();
174    }
175
176    /**
177     * Install all the fragments kept in {@link #mRestoredFragments}.
178     *
179     * Must be called at the end of {@link EmailActivity#onCreate}.
180     */
181    public final void installRestoredFragments() {
182        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
183            Log.d(Logging.LOG_TAG, this + " installRestoredFragments");
184        }
185
186        mHoldFragmentInstallation = false;
187
188        // Install all the fragments restored by the framework.
189        for (Fragment fragment : mRestoredFragments) {
190            installFragment(fragment);
191        }
192        mRestoredFragments.clear();
193    }
194
195    /**
196     * Handles the {@link android.app.Activity#onSaveInstanceState} callback.
197     */
198    public void onSaveInstanceState(Bundle outState) {
199        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
200            Log.d(Logging.LOG_TAG, this + " onSaveInstanceState");
201        }
202    }
203
204    /**
205     * Handles the {@link android.app.Activity#onRestoreInstanceState} callback.
206     */
207    public void restoreInstanceState(Bundle savedInstanceState) {
208        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
209            Log.d(Logging.LOG_TAG, this + " restoreInstanceState");
210        }
211    }
212
213    /**
214     * Handles the {@link android.app.Activity#onAttachFragment} callback.
215     *
216     * If the activity has already been created, we initialize the fragment here.  Otherwise we
217     * keep the fragment in {@link #mRestoredFragments} and initialize it after the activity's
218     * onCreate.
219     */
220    public final void onAttachFragment(Fragment fragment) {
221        if (mHoldFragmentInstallation) {
222            // Fragment being restored by the framework during the activity recreation.
223            mRestoredFragments.add(fragment);
224            return;
225        }
226        installFragment(fragment);
227    }
228
229    void installFragment(Fragment fragment) {
230        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
231            Log.d(Logging.LOG_TAG, this + " installFragment  fragment=" + fragment);
232        }
233    }
234
235    // not used
236    void popBackStack(FragmentManager fm, String name, int flags) {
237        fm.popBackStackImmediate(name, flags);
238    }
239
240    void commitFragmentTransaction(FragmentTransaction ft) {
241        ft.commit();
242        mActivity.getFragmentManager().executePendingTransactions();
243    }
244
245
246    /**
247     * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
248     *
249     * @see #getActualAccountId()
250     */
251    public abstract long getUIAccountId();
252
253    /**
254     * @return true if an account is selected, or the current view is the combined view.
255     */
256    public final boolean isAccountSelected() {
257        return getUIAccountId() != NO_ACCOUNT;
258    }
259
260    /**
261     * @return if an actual account is selected.  (i.e. {@link Account#ACCOUNT_ID_COMBINED_VIEW}
262     * is not considered "actual".s)
263     */
264    public final boolean isActualAccountSelected() {
265        return isAccountSelected() && (getUIAccountId() != Account.ACCOUNT_ID_COMBINED_VIEW);
266    }
267
268    /**
269     * @return the currently selected account ID.  If the current view is the combined view,
270     * it'll return {@link #NO_ACCOUNT}.
271     *
272     * @see #getUIAccountId()
273     */
274    public final long getActualAccountId() {
275        return isActualAccountSelected() ? getUIAccountId() : NO_ACCOUNT;
276    }
277
278    /**
279     * Show the default view for the given account.
280     *
281     * @param accountId ID of the account to load.  Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
282     *     Must never be {@link #NO_ACCOUNT}.
283     */
284    public abstract void openAccount(long accountId);
285
286    /**
287     * Loads the given account and optionally selects the given mailbox and message.  Used to open
288     * a particular view at a request from outside of the activity, such as the widget.
289     *
290     * @param accountId ID of the account to load.  Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
291     *     Must never be {@link #NO_ACCOUNT}.
292     * @param mailboxId ID of the mailbox to load. If {@link #NO_MAILBOX}, load the account's inbox.
293     * @param messageId ID of the message to load. If {@link #NO_MESSAGE}, do not open a message.
294     */
295    public abstract void open(long accountId, long mailboxId, long messageId);
296
297    /**
298     * Performs the back action.
299     *
300     * @param isSystemBackKey <code>true</code> if the system back key was pressed.
301     * <code>false</code> if it's caused by the "home" icon click on the action bar.
302     */
303    public abstract boolean onBackPressed(boolean isSystemBackKey);
304
305    /**
306     * Handles the {@link android.app.Activity#onCreateOptionsMenu} callback.
307     */
308    public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) {
309        inflater.inflate(R.menu.email_activity_options, menu);
310        return true;
311    }
312
313    /**
314     * Handles the {@link android.app.Activity#onPrepareOptionsMenu} callback.
315     */
316    public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) {
317
318        // Update the refresh button.
319        MenuItem item = menu.findItem(R.id.refresh);
320        if (isRefreshEnabled()) {
321            item.setVisible(true);
322            if (isRefreshInProgress()) {
323                item.setActionView(R.layout.action_bar_indeterminate_progress);
324            } else {
325                item.setActionView(null);
326            }
327        } else {
328            item.setVisible(false);
329        }
330        return true;
331    }
332
333    /**
334     * Handles the {@link android.app.Activity#onOptionsItemSelected} callback.
335     *
336     * @return true if the option item is handled.
337     */
338    public boolean onOptionsItemSelected(MenuItem item) {
339        switch (item.getItemId()) {
340            case android.R.id.home:
341                // Comes from the action bar when the app icon on the left is pressed.
342                // It works like a back press, but it won't close the activity.
343                return onBackPressed(false);
344            case R.id.compose:
345                return onCompose();
346            case R.id.refresh:
347                onRefresh();
348                return true;
349            case R.id.account_settings:
350                return onAccountSettings();
351        }
352        return false;
353    }
354
355    /**
356     * Opens the message compose activity.
357     */
358    private boolean onCompose() {
359        if (!isAccountSelected()) {
360            return false; // this shouldn't really happen
361        }
362        MessageCompose.actionCompose(mActivity, getActualAccountId());
363        return true;
364    }
365
366    /**
367     * Handles the "Settings" option item.  Opens the settings activity.
368     */
369    private boolean onAccountSettings() {
370        AccountSettings.actionSettings(mActivity, getActualAccountId());
371        return true;
372    }
373
374    /**
375     * STOPSHIP For experimental UI.  Remove this.
376     *
377     * @return mailbox ID which we search for messages.
378     */
379    public abstract long getSearchMailboxId();
380
381    /**
382     * STOPSHIP For experimental UI.  Remove this.
383     *
384     * @return mailbox ID for "mailbox settings" option.
385     */
386    public abstract long getMailboxSettingsMailboxId();
387
388    /**
389     * STOPSHIP For experimental UI.  Make it abstract protected.
390     *
391     * Performs "refesh".
392     */
393    public abstract void onRefresh();
394
395    /**
396     * @return true if refresh is in progress for the current mailbox.
397     */
398    protected abstract boolean isRefreshInProgress();
399
400    /**
401     * @return true if the UI should enable the "refresh" command.
402     */
403    protected abstract boolean isRefreshEnabled();
404
405
406    /**
407     * Start/stop the "refresh" animation on the action bar according to the current refresh state.
408     *
409     * (We start the animation if {@link #isRefreshInProgress} returns true,
410     * and stop otherwise.)
411     */
412    protected void updateRefreshProgress() {
413        mActivity.invalidateOptionsMenu();
414    }
415}
416