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