DialtactsActivity.java revision c15062754af08cadc50b4b8ec89c2175a4bec1fe
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.contacts.activities;
18
19import com.android.contacts.R;
20import com.android.contacts.calllog.CallLogFragment;
21import com.android.contacts.dialpad.DialpadFragment;
22import com.android.contacts.interactions.ImportExportInteraction;
23import com.android.contacts.list.ContactListFilter;
24import com.android.contacts.list.ContactsIntentResolver;
25import com.android.contacts.list.ContactsRequest;
26import com.android.contacts.list.DefaultContactBrowseListFragment;
27import com.android.contacts.list.DirectoryListLoader;
28import com.android.contacts.list.OnContactBrowserActionListener;
29import com.android.contacts.preference.ContactsPreferenceActivity;
30import com.android.internal.telephony.ITelephony;
31
32import android.app.ActionBar;
33import android.app.ActionBar.Tab;
34import android.app.ActionBar.TabListener;
35import android.app.Activity;
36import android.app.Dialog;
37import android.app.Fragment;
38import android.app.FragmentManager;
39import android.app.FragmentTransaction;
40import android.content.Intent;
41import android.content.SharedPreferences;
42import android.net.Uri;
43import android.os.Bundle;
44import android.os.RemoteException;
45import android.os.ServiceManager;
46import android.provider.CallLog.Calls;
47import android.provider.ContactsContract;
48import android.provider.ContactsContract.Contacts;
49import android.provider.Settings;
50import android.provider.ContactsContract.Intents.UI;
51import android.util.Log;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55
56/**
57 * The dialer activity that has one tab with the virtual 12key
58 * dialer, a tab with recent calls in it, a tab with the contacts and
59 * a tab with the favorite. This is the container and the tabs are
60 * embedded using intents.
61 * The dialer tab's title is 'phone', a more common name (see strings.xml).
62 */
63public class DialtactsActivity extends Activity {
64    private static final String TAG = "DialtactsActivity";
65
66    private static final int TAB_INDEX_DIALER = 0;
67    private static final int TAB_INDEX_CALL_LOG = 1;
68    private static final int TAB_INDEX_CONTACTS = 2;
69    private static final int TAB_INDEX_FAVORITES = 3;
70
71    public static final String EXTRA_IGNORE_STATE = "ignore-state";
72
73    /** Name of the dialtacts shared preferences */
74    static final String PREFS_DIALTACTS = "dialtacts";
75    /** If true, when handling the contacts intent the favorites tab will be shown instead */
76    static final String PREF_FAVORITES_AS_CONTACTS = "favorites_as_contacts";
77    static final boolean PREF_FAVORITES_AS_CONTACTS_DEFAULT = false;
78
79    /** Last manually selected tab index */
80    private static final String PREF_LAST_MANUALLY_SELECTED_TAB = "last_manually_selected_tab";
81    private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
82
83    private String mFilterText;
84    private Uri mDialUri;
85    private DialpadFragment mDialpadFragment;
86    private CallLogFragment mCallLogFragment;
87    private DefaultContactBrowseListFragment mContactsFragment;
88    private DefaultContactBrowseListFragment mFavoritesFragment;
89    private ImportExportInteraction mImportExportInteraction;
90
91    /**
92     * The index of the tab that has last been manually selected (the user clicked on a tab).
93     * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
94     */
95    private int mLastManuallySelectedTab;
96
97    @Override
98    protected void onCreate(Bundle icicle) {
99        super.onCreate(icicle);
100
101        final Intent intent = getIntent();
102        fixIntent(intent);
103
104        setContentView(R.layout.dialtacts_activity);
105
106        final FragmentManager fragmentManager = getFragmentManager();
107        mDialpadFragment = (DialpadFragment) fragmentManager
108                .findFragmentById(R.id.dialpad_fragment);
109        mCallLogFragment = (CallLogFragment) fragmentManager
110                .findFragmentById(R.id.call_log_fragment);
111        mContactsFragment = (DefaultContactBrowseListFragment) fragmentManager
112                .findFragmentById(R.id.contacts_fragment);
113        mFavoritesFragment = (DefaultContactBrowseListFragment) fragmentManager
114                .findFragmentById(R.id.favorites_fragment);
115
116        // Hide all tabs (the current tab will later be reshown once a tab is selected)
117        final FragmentTransaction transaction = fragmentManager.beginTransaction();
118        transaction.hide(mDialpadFragment);
119        transaction.hide(mCallLogFragment);
120        transaction.hide(mContactsFragment);
121        transaction.hide(mFavoritesFragment);
122        transaction.commit();
123
124        // Setup the ActionBar tabs (the order matches the tab-index contants TAB_INDEX_*)
125        setupDialer();
126        setupCallLog();
127        setupContacts();
128        setupFavorites();
129        getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
130        getActionBar().setDisplayShowTitleEnabled(false);
131        getActionBar().setDisplayShowHomeEnabled(false);
132
133        // Load the last manually loaded tab
134        final SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
135        mLastManuallySelectedTab = prefs.getInt(PREF_LAST_MANUALLY_SELECTED_TAB,
136                PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT);
137
138        setCurrentTab(intent);
139
140        if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
141                && icicle == null) {
142            setupFilterText(intent);
143        }
144    }
145
146    @Override
147    protected void onPause() {
148        super.onPause();
149
150        final int currentTabIndex = getActionBar().getSelectedTab().getPosition();
151        final SharedPreferences.Editor editor =
152                getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE).edit();
153        if (currentTabIndex == TAB_INDEX_CONTACTS || currentTabIndex == TAB_INDEX_FAVORITES) {
154            editor.putBoolean(PREF_FAVORITES_AS_CONTACTS, currentTabIndex == TAB_INDEX_FAVORITES);
155        }
156        editor.putInt(PREF_LAST_MANUALLY_SELECTED_TAB, mLastManuallySelectedTab);
157
158        editor.apply();
159    }
160
161    private void fixIntent(Intent intent) {
162        // This should be cleaned up: the call key used to send an Intent
163        // that just said to go to the recent calls list.  It now sends this
164        // abstract action, but this class hasn't been rewritten to deal with it.
165        if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
166            intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
167            intent.putExtra("call_key", true);
168            setIntent(intent);
169        }
170    }
171
172    private void setupDialer() {
173        final Tab tab = getActionBar().newTab();
174        tab.setText(R.string.dialerIconLabel);
175        tab.setTabListener(new TabChangeListener(mDialpadFragment));
176        tab.setIcon(R.drawable.ic_tab_dialer);
177        getActionBar().addTab(tab);
178        mDialpadFragment.resolveIntent();
179    }
180
181    private void setupCallLog() {
182        final Tab tab = getActionBar().newTab();
183        tab.setText(R.string.recentCallsIconLabel);
184        tab.setIcon(R.drawable.ic_tab_recent);
185        tab.setTabListener(new TabChangeListener(mCallLogFragment));
186        getActionBar().addTab(tab);
187    }
188
189    private void setupContacts() {
190        final Tab tab = getActionBar().newTab();
191        tab.setText(R.string.contactsIconLabel);
192        tab.setIcon(R.drawable.ic_tab_contacts);
193        tab.setTabListener(new TabChangeListener(mContactsFragment));
194        getActionBar().addTab(tab);
195
196        // TODO: We should not artificially create Intents and put them into the Fragment.
197        // It would be nicer to directly pass in the UI constant
198        Intent intent = new Intent(UI.LIST_ALL_CONTACTS_ACTION);
199        intent.setClass(this, ContactBrowserActivity.class);
200
201        ContactsIntentResolver resolver = new ContactsIntentResolver(this);
202        ContactsRequest request = resolver.resolveIntent(intent);
203        final ContactListFilter filter = new ContactListFilter(
204                ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
205        mContactsFragment.setFilter(filter, false);
206        mContactsFragment.setSearchMode(request.isSearchMode());
207        mContactsFragment.setQueryString(request.getQueryString(), false);
208        mContactsFragment.setContactsRequest(request);
209        mContactsFragment.setDirectorySearchMode(request.isDirectorySearchEnabled()
210                ? DirectoryListLoader.SEARCH_MODE_DEFAULT
211                : DirectoryListLoader.SEARCH_MODE_NONE);
212        mContactsFragment.setOnContactListActionListener(mListFragmentListener);
213    }
214
215    private void setupFavorites() {
216        final Tab tab = getActionBar().newTab();
217        tab.setText(R.string.contactsFavoritesLabel);
218        tab.setIcon(R.drawable.ic_tab_starred);
219        tab.setTabListener(new TabChangeListener(mFavoritesFragment));
220        getActionBar().addTab(tab);
221
222        // TODO: We should not artificially create Intents and put them into the Fragment.
223        // It would be nicer to directly pass in the UI constant
224        Intent intent = new Intent(UI.LIST_STREQUENT_ACTION);
225        intent.setClass(this, ContactBrowserActivity.class);
226
227        ContactsIntentResolver resolver = new ContactsIntentResolver(this);
228        ContactsRequest request = resolver.resolveIntent(intent);
229        final ContactListFilter filter = new ContactListFilter(
230                ContactListFilter.FILTER_TYPE_STARRED);
231        mFavoritesFragment.setFilter(filter, false);
232        mFavoritesFragment.setSearchMode(request.isSearchMode());
233        mFavoritesFragment.setQueryString(request.getQueryString(), false);
234        mFavoritesFragment.setContactsRequest(request);
235        mFavoritesFragment.setDirectorySearchMode(request.isDirectorySearchEnabled()
236                ? DirectoryListLoader.SEARCH_MODE_DEFAULT
237                : DirectoryListLoader.SEARCH_MODE_NONE);
238        mFavoritesFragment.setOnContactListActionListener(mListFragmentListener);
239    }
240
241    /**
242     * Returns true if the intent is due to hitting the green send key while in a call.
243     *
244     * @param intent the intent that launched this activity
245     * @param recentCallsRequest true if the intent is requesting to view recent calls
246     * @return true if the intent is due to hitting the green send key while in a call
247     */
248    private boolean isSendKeyWhileInCall(final Intent intent, final boolean recentCallsRequest) {
249        // If there is a call in progress go to the call screen
250        if (recentCallsRequest) {
251            final boolean callKey = intent.getBooleanExtra("call_key", false);
252
253            try {
254                ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
255                if (callKey && phone != null && phone.showCallScreen()) {
256                    return true;
257                }
258            } catch (RemoteException e) {
259                Log.e(TAG, "Failed to handle send while in call", e);
260            }
261        }
262
263        return false;
264    }
265
266    /**
267     * Sets the current tab based on the intent's request type
268     *
269     * @param intent Intent that contains information about which tab should be selected
270     */
271    private void setCurrentTab(Intent intent) {
272        // If we got here by hitting send and we're in call forward along to the in-call activity
273        final boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.getType());
274        if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
275            finish();
276            return;
277        }
278
279        // Tell the children activities that they should ignore any possible saved
280        // state and instead reload their state from the parent's intent
281        intent.putExtra(EXTRA_IGNORE_STATE, true);
282
283        // Remember the old manually selected tab index so that it can be restored if it is
284        // overwritten by one of the programmatic tab selections
285        final int savedTabIndex = mLastManuallySelectedTab;
286
287        // Choose the tab based on the inbound intent
288        if (intent.getBooleanExtra(ContactsFrontDoor.EXTRA_FRONT_DOOR, false)) {
289            // Launched through the contacts front door, set the proper contacts tab (sticky
290            // between favorites and contacts)
291            SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
292            boolean favoritesAsContacts = prefs.getBoolean(PREF_FAVORITES_AS_CONTACTS,
293                    PREF_FAVORITES_AS_CONTACTS_DEFAULT);
294            if (favoritesAsContacts) {
295                getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_FAVORITES));
296            } else {
297                getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_CONTACTS));
298            }
299        } else {
300            // Not launched through the front door, look at the component to determine the tab
301            String componentName = intent.getComponent().getClassName();
302            if (getClass().getName().equals(componentName)) {
303                if (recentCallsRequest) {
304                    getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_CALL_LOG));
305                } else {
306                    getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_DIALER));
307                }
308            } else {
309                getActionBar().selectTab(getActionBar().getTabAt(mLastManuallySelectedTab));
310            }
311        }
312
313        // Restore to the previous manual selection
314        mLastManuallySelectedTab = savedTabIndex;
315
316        // Tell the children activities that they should honor their saved states
317        // instead of the state from the parent's intent
318        intent.putExtra(EXTRA_IGNORE_STATE, false);
319    }
320
321    @Override
322    public void onNewIntent(Intent newIntent) {
323        setIntent(newIntent);
324        fixIntent(newIntent);
325        setCurrentTab(newIntent);
326        final String action = newIntent.getAction();
327        if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
328            setupFilterText(newIntent);
329        } else if (isDialIntent(newIntent)) {
330            setupDialUri(newIntent);
331        }
332    }
333
334    /** Returns true if the given intent contains a phone number to populate the dialer with */
335    private boolean isDialIntent(Intent intent) {
336        final String action = intent.getAction();
337        if (Intent.ACTION_DIAL.equals(action)) {
338            return true;
339        }
340        if (Intent.ACTION_VIEW.equals(action)) {
341            final Uri data = intent.getData();
342            if (data != null && "tel".equals(data.getScheme())) {
343                return true;
344            }
345        }
346        return false;
347    }
348
349    /**
350     * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
351     * This text originally came from a FILTER_CONTACTS_ACTION intent received
352     * by this activity. The stored text will then be cleared after after this
353     * method returns.
354     *
355     * @return The stored filter text
356     */
357    public String getAndClearFilterText() {
358        String filterText = mFilterText;
359        mFilterText = null;
360        return filterText;
361    }
362
363    /**
364     * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
365     * This is so child activities can check if they are supposed to display a filter.
366     *
367     * @param intent The intent received in {@link #onNewIntent(Intent)}
368     */
369    private void setupFilterText(Intent intent) {
370        // If the intent was relaunched from history, don't apply the filter text.
371        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
372            return;
373        }
374        String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
375        if (filter != null && filter.length() > 0) {
376            mFilterText = filter;
377        }
378    }
379
380    /**
381     * Retrieves the uri stored in {@link #setupDialUri(Intent)}. This uri
382     * originally came from a dial intent received by this activity. The stored
383     * uri will then be cleared after after this method returns.
384     *
385     * @return The stored uri
386     */
387    public Uri getAndClearDialUri() {
388        Uri dialUri = mDialUri;
389        mDialUri = null;
390        return dialUri;
391    }
392
393    /**
394     * Stores the uri associated with a dial intent. This is so child activities can
395     * check if they are supposed to display new dial info.
396     *
397     * @param intent The intent received in {@link #onNewIntent(Intent)}
398     */
399    private void setupDialUri(Intent intent) {
400        // If the intent was relaunched from history, don't reapply the intent.
401        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
402            return;
403        }
404        mDialUri = intent.getData();
405    }
406
407    @Override
408    public void onBackPressed() {
409        if (isTaskRoot()) {
410            // Instead of stopping, simply push this to the back of the stack.
411            // This is only done when running at the top of the stack;
412            // otherwise, we have been launched by someone else so need to
413            // allow the user to go back to the caller.
414            moveTaskToBack(false);
415        } else {
416            super.onBackPressed();
417        }
418    }
419
420    @Override
421    protected void onPostCreate(Bundle savedInstanceState) {
422        super.onPostCreate(savedInstanceState);
423
424        // Pass this lifecycle event down to the fragment
425        mDialpadFragment.onPostCreate();
426    }
427
428    /**
429     * Tab change listener that is instantiated once for each tab. Handles showing/hiding tabs
430     * and remembers manual tab selections
431     */
432    private class TabChangeListener implements TabListener {
433        private final Fragment mFragment;
434
435        public TabChangeListener(Fragment fragment) {
436            mFragment = fragment;
437        }
438
439        @Override
440        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
441            ft.hide(mFragment);
442        }
443
444        @Override
445        public void onTabSelected(Tab tab, FragmentTransaction ft) {
446            ft.show(mFragment);
447
448            // Remember this tab index. This function is also called, if the tab is set
449            // automatically in which case the setter (setCurrentTab) has to set this to its old
450            // value afterwards
451            mLastManuallySelectedTab = tab.getPosition();
452        }
453
454        @Override
455        public void onTabReselected(Tab tab, FragmentTransaction ft) {
456        }
457    }
458
459    private OnContactBrowserActionListener mListFragmentListener =
460            new OnContactBrowserActionListener() {
461        @Override
462        public void onViewContactAction(Uri contactLookupUri) {
463            startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
464        }
465
466        @Override
467        public void onSmsContactAction(Uri contactUri) {
468        }
469
470        @Override
471        public void onSelectionChange() {
472        }
473
474        @Override
475        public void onRemoveFromFavoritesAction(Uri contactUri) {
476        }
477
478        @Override
479        public void onInvalidSelection() {
480        }
481
482        @Override
483        public void onFinishAction() {
484        }
485
486        @Override
487        public void onEditContactAction(Uri contactLookupUri) {
488        }
489
490        @Override
491        public void onDeleteContactAction(Uri contactUri) {
492        }
493
494        @Override
495        public void onCreateNewContactAction() {
496        }
497
498        @Override
499        public void onCallContactAction(Uri contactUri) {
500        }
501
502        @Override
503        public void onAddToFavoritesAction(Uri contactUri) {
504        }
505    };
506
507    @Override
508    public boolean onCreateOptionsMenu(Menu menu) {
509        // For now, create the menu in here. It would be nice to do this in the Fragment,
510        // but that Fragment is re-used in other views.
511        final ActionBar actionBar = getActionBar();
512        if (actionBar == null) return false;
513        final Tab tab = actionBar.getSelectedTab();
514        if (tab == null) return false;
515        final int tabIndex = tab.getPosition();
516        if (tabIndex != TAB_INDEX_CONTACTS && tabIndex != TAB_INDEX_FAVORITES) return false;
517
518        MenuInflater inflater = getMenuInflater();
519        inflater.inflate(R.menu.list, menu);
520        return true;
521    }
522
523    @Override
524    public boolean onOptionsItemSelected(MenuItem item) {
525        // This is currently a copy of the equivalent code of ContactBrowserActivity (with the
526        // exception of menu_add, because we do not select items in the list).
527        // Should be consolidated
528        switch (item.getItemId()) {
529        case R.id.menu_settings: {
530            final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
531            startActivity(intent);
532            return true;
533        }
534        case R.id.menu_search: {
535            onSearchRequested();
536            return true;
537        }
538        case R.id.menu_add: {
539            final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
540            startActivity(intent);
541            return true;
542        }
543        case R.id.menu_import_export: {
544            getImportExportInteraction().startInteraction();
545            return true;
546        }
547        case R.id.menu_accounts: {
548            final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
549            intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
550                ContactsContract.AUTHORITY
551            });
552            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
553            startActivity(intent);
554            return true;
555        }
556        default:
557            return super.onOptionsItemSelected(item);
558        }
559    }
560
561    @Override
562    protected Dialog onCreateDialog(int id, Bundle bundle) {
563        Dialog dialog = getImportExportInteraction().onCreateDialog(id, bundle);
564        if (dialog != null) return dialog;
565        return super.onCreateDialog(id, bundle);
566    }
567
568    private ImportExportInteraction getImportExportInteraction() {
569        if (mImportExportInteraction == null) {
570            mImportExportInteraction = new ImportExportInteraction(this);
571        }
572        return mImportExportInteraction;
573    }
574}
575