DialtactsActivity.java revision b1f0e5e8a4de69bd80276742900441e930fa4c86
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    @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        mStrequentFragment = (StrequentContactListFragment) 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(mStrequentFragment);
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        // TODO: Temporarily disable tab text labels (in all 4 tabs in this
175        //   activity) so that the current tabs will all fit onscreen in
176        //   portrait (bug 4520620).  (Also note we do setText("") rather
177        //   leaving the text null, to work around bug 4521549.)
178        tab.setText("");  // R.string.dialerIconLabel
179        tab.setTabListener(new TabChangeListener(mDialpadFragment));
180        tab.setIcon(R.drawable.ic_tab_dialer);
181        getActionBar().addTab(tab);
182        mDialpadFragment.resolveIntent();
183    }
184
185    private void setupCallLog() {
186        final Tab tab = getActionBar().newTab();
187        tab.setText("");  // R.string.recentCallsIconLabel
188        tab.setIcon(R.drawable.ic_tab_recent);
189        tab.setTabListener(new TabChangeListener(mCallLogFragment));
190        getActionBar().addTab(tab);
191    }
192
193    private void setupContacts() {
194        final Tab tab = getActionBar().newTab();
195        tab.setText("");  // R.string.contactsIconLabel
196        tab.setIcon(R.drawable.ic_tab_contacts);
197        tab.setTabListener(new TabChangeListener(mContactsFragment));
198        getActionBar().addTab(tab);
199
200        // TODO: We should not artificially create Intents and put them into the Fragment.
201        // It would be nicer to directly pass in the UI constant
202        Intent intent = new Intent(UI.LIST_ALL_CONTACTS_ACTION);
203        intent.setClass(this, PeopleActivity.class);
204
205        ContactsIntentResolver resolver = new ContactsIntentResolver(this);
206        ContactsRequest request = resolver.resolveIntent(intent);
207        final ContactListFilter filter = ContactListFilter.createFilterWithType(
208                ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
209        mContactsFragment.setFilter(filter, false);
210        mContactsFragment.setSearchMode(request.isSearchMode());
211        mContactsFragment.setQueryString(request.getQueryString(), false);
212        mContactsFragment.setContactsRequest(request);
213        mContactsFragment.setDirectorySearchMode(request.isDirectorySearchEnabled()
214                ? DirectoryListLoader.SEARCH_MODE_DEFAULT
215                : DirectoryListLoader.SEARCH_MODE_NONE);
216        mContactsFragment.setOnContactListActionListener(mListFragmentListener);
217    }
218
219    private void setupFavorites() {
220        final Tab tab = getActionBar().newTab();
221        tab.setText("");  // R.string.contactsFavoritesLabel
222        tab.setIcon(R.drawable.ic_tab_starred);
223        tab.setTabListener(new TabChangeListener(mStrequentFragment));
224        getActionBar().addTab(tab);
225        mStrequentFragment.setListener(mStrequentListener);
226    }
227
228    /**
229     * Returns true if the intent is due to hitting the green send key while in a call.
230     *
231     * @param intent the intent that launched this activity
232     * @param recentCallsRequest true if the intent is requesting to view recent calls
233     * @return true if the intent is due to hitting the green send key while in a call
234     */
235    private boolean isSendKeyWhileInCall(final Intent intent, final boolean recentCallsRequest) {
236        // If there is a call in progress go to the call screen
237        if (recentCallsRequest) {
238            final boolean callKey = intent.getBooleanExtra("call_key", false);
239
240            try {
241                ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
242                if (callKey && phone != null && phone.showCallScreen()) {
243                    return true;
244                }
245            } catch (RemoteException e) {
246                Log.e(TAG, "Failed to handle send while in call", e);
247            }
248        }
249
250        return false;
251    }
252
253    /**
254     * Sets the current tab based on the intent's request type
255     *
256     * @param intent Intent that contains information about which tab should be selected
257     */
258    private void setCurrentTab(Intent intent) {
259        // If we got here by hitting send and we're in call forward along to the in-call activity
260        final boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.getType());
261        if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
262            finish();
263            return;
264        }
265
266        // Tell the children activities that they should ignore any possible saved
267        // state and instead reload their state from the parent's intent
268        intent.putExtra(EXTRA_IGNORE_STATE, true);
269
270        // Remember the old manually selected tab index so that it can be restored if it is
271        // overwritten by one of the programmatic tab selections
272        final int savedTabIndex = mLastManuallySelectedTab;
273
274        // Look at the component to determine the tab
275        String componentName = intent.getComponent().getClassName();
276        if (getClass().getName().equals(componentName)) {
277            if (recentCallsRequest) {
278                getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_CALL_LOG));
279            } else {
280                getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_DIALER));
281            }
282        } else {
283            getActionBar().selectTab(getActionBar().getTabAt(mLastManuallySelectedTab));
284        }
285
286        // Restore to the previous manual selection
287        mLastManuallySelectedTab = savedTabIndex;
288
289        // Tell the children activities that they should honor their saved states
290        // instead of the state from the parent's intent
291        intent.putExtra(EXTRA_IGNORE_STATE, false);
292    }
293
294    @Override
295    public void onNewIntent(Intent newIntent) {
296        setIntent(newIntent);
297        fixIntent(newIntent);
298        setCurrentTab(newIntent);
299        final String action = newIntent.getAction();
300        if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
301            setupFilterText(newIntent);
302        } else if (isDialIntent(newIntent)) {
303            setupDialUri(newIntent);
304        }
305        // Fill in a phone number again.
306        mDialpadFragment.resolveIntent();
307    }
308
309    /** Returns true if the given intent contains a phone number to populate the dialer with */
310    private boolean isDialIntent(Intent intent) {
311        final String action = intent.getAction();
312        if (Intent.ACTION_DIAL.equals(action)) {
313            return true;
314        }
315        if (Intent.ACTION_VIEW.equals(action)) {
316            final Uri data = intent.getData();
317            if (data != null && "tel".equals(data.getScheme())) {
318                return true;
319            }
320        }
321        return false;
322    }
323
324    /**
325     * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
326     * This text originally came from a FILTER_CONTACTS_ACTION intent received
327     * by this activity. The stored text will then be cleared after after this
328     * method returns.
329     *
330     * @return The stored filter text
331     */
332    public String getAndClearFilterText() {
333        String filterText = mFilterText;
334        mFilterText = null;
335        return filterText;
336    }
337
338    /**
339     * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
340     * This is so child activities can check if they are supposed to display a filter.
341     *
342     * @param intent The intent received in {@link #onNewIntent(Intent)}
343     */
344    private void setupFilterText(Intent intent) {
345        // If the intent was relaunched from history, don't apply the filter text.
346        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
347            return;
348        }
349        String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
350        if (filter != null && filter.length() > 0) {
351            mFilterText = filter;
352        }
353    }
354
355    /**
356     * Retrieves the uri stored in {@link #setupDialUri(Intent)}. This uri
357     * originally came from a dial intent received by this activity. The stored
358     * uri will then be cleared after after this method returns.
359     *
360     * @return The stored uri
361     */
362    public Uri getAndClearDialUri() {
363        Uri dialUri = mDialUri;
364        mDialUri = null;
365        return dialUri;
366    }
367
368    /**
369     * Stores the uri associated with a dial intent. This is so child activities can
370     * check if they are supposed to display new dial info.
371     *
372     * @param intent The intent received in {@link #onNewIntent(Intent)}
373     */
374    private void setupDialUri(Intent intent) {
375        // If the intent was relaunched from history, don't reapply the intent.
376        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
377            return;
378        }
379        mDialUri = intent.getData();
380    }
381
382    @Override
383    public void onBackPressed() {
384        if (isTaskRoot()) {
385            // Instead of stopping, simply push this to the back of the stack.
386            // This is only done when running at the top of the stack;
387            // otherwise, we have been launched by someone else so need to
388            // allow the user to go back to the caller.
389            moveTaskToBack(false);
390        } else {
391            super.onBackPressed();
392        }
393    }
394
395    @Override
396    protected void onPostCreate(Bundle savedInstanceState) {
397        super.onPostCreate(savedInstanceState);
398
399        // Pass this lifecycle event down to the fragment
400        mDialpadFragment.onPostCreate();
401    }
402
403    /**
404     * Tab change listener that is instantiated once for each tab. Handles showing/hiding tabs
405     * and remembers manual tab selections
406     */
407    private class TabChangeListener implements TabListener {
408        private final Fragment mFragment;
409
410        public TabChangeListener(Fragment fragment) {
411            mFragment = fragment;
412        }
413
414        @Override
415        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
416            ft.hide(mFragment);
417        }
418
419        @Override
420        public void onTabSelected(Tab tab, FragmentTransaction ft) {
421            ft.show(mFragment);
422
423            // Remember this tab index. This function is also called, if the tab is set
424            // automatically in which case the setter (setCurrentTab) has to set this to its old
425            // value afterwards
426            mLastManuallySelectedTab = tab.getPosition();
427        }
428
429        @Override
430        public void onTabReselected(Tab tab, FragmentTransaction ft) {
431        }
432    }
433
434    private OnContactBrowserActionListener mListFragmentListener =
435            new OnContactBrowserActionListener() {
436        @Override
437        public void onViewContactAction(Uri contactLookupUri) {
438            startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
439        }
440
441        @Override
442        public void onSmsContactAction(Uri contactUri) {
443        }
444
445        @Override
446        public void onSelectionChange() {
447        }
448
449        @Override
450        public void onRemoveFromFavoritesAction(Uri contactUri) {
451        }
452
453        @Override
454        public void onInvalidSelection() {
455        }
456
457        @Override
458        public void onFinishAction() {
459        }
460
461        @Override
462        public void onEditContactAction(Uri contactLookupUri) {
463        }
464
465        @Override
466        public void onDeleteContactAction(Uri contactUri) {
467        }
468
469        @Override
470        public void onCreateNewContactAction() {
471        }
472
473        @Override
474        public void onCallContactAction(Uri contactUri) {
475            PhoneNumberInteraction.startInteractionForPhoneCall(
476                    DialtactsActivity.this, contactUri);
477        }
478
479        @Override
480        public void onAddToFavoritesAction(Uri contactUri) {
481        }
482    };
483
484    private StrequentContactListFragment.Listener mStrequentListener =
485            new StrequentContactListFragment.Listener() {
486        @Override
487        public void onContactSelected(Uri contactUri) {
488            PhoneNumberInteraction.startInteractionForPhoneCall(
489                    DialtactsActivity.this, contactUri);
490        }
491    };
492
493    @Override
494    public boolean onCreateOptionsMenu(Menu menu) {
495        // For now, create the menu in here. It would be nice to do this in the Fragment,
496        // but that Fragment is re-used in other views.
497        final ActionBar actionBar = getActionBar();
498        if (actionBar == null) return false;
499        final Tab tab = actionBar.getSelectedTab();
500        if (tab == null) return false;
501        final int tabIndex = tab.getPosition();
502        if (tabIndex != TAB_INDEX_CONTACTS && tabIndex != TAB_INDEX_FAVORITES) return false;
503
504        MenuInflater inflater = getMenuInflater();
505        inflater.inflate(R.menu.list, menu);
506        return true;
507    }
508
509    @Override
510    public boolean onOptionsItemSelected(MenuItem item) {
511        // This is currently a copy of the equivalent code of PeopleActivity (with the
512        // exception of menu_add, because we do not select items in the list).
513        // Should be consolidated
514        switch (item.getItemId()) {
515        case R.id.menu_settings: {
516            final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
517            startActivity(intent);
518            return true;
519        }
520        case R.id.menu_search: {
521            onSearchRequested();
522            return true;
523        }
524        case R.id.menu_add: {
525            final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
526            startActivity(intent);
527            return true;
528        }
529        case R.id.menu_import_export: {
530            ImportExportDialogFragment.show(getFragmentManager());
531            return true;
532        }
533        case R.id.menu_accounts: {
534            final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
535            intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
536                ContactsContract.AUTHORITY
537            });
538            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
539            startActivity(intent);
540            return true;
541        }
542        default:
543            return super.onOptionsItemSelected(item);
544        }
545    }
546}
547