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