ContactDetailActivity.java revision e405753872c29c04e4099ce5c68313e9580687c8
1/*
2 * Copyright (C) 2010 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.ContactLoader;
20import com.android.contacts.ContactSaveService;
21import com.android.contacts.ContactsActivity;
22import com.android.contacts.ContactsSearchManager;
23import com.android.contacts.R;
24import com.android.contacts.detail.ContactDetailDisplayUtils;
25import com.android.contacts.detail.ContactDetailFragment;
26import com.android.contacts.detail.ContactDetailFragmentCarousel;
27import com.android.contacts.detail.ContactDetailTabCarousel;
28import com.android.contacts.detail.ContactDetailUpdatesFragment;
29import com.android.contacts.detail.ContactLoaderFragment;
30import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
31import com.android.contacts.interactions.ContactDeletionInteraction;
32import com.android.contacts.util.PhoneCapabilityTester;
33
34import android.accounts.Account;
35import android.app.ActionBar;
36import android.app.Fragment;
37import android.app.FragmentManager;
38import android.app.FragmentTransaction;
39import android.content.ActivityNotFoundException;
40import android.content.ContentValues;
41import android.content.Context;
42import android.content.Intent;
43import android.net.Uri;
44import android.os.Bundle;
45import android.os.Handler;
46import android.support.v13.app.FragmentPagerAdapter;
47import android.support.v4.view.ViewPager;
48import android.support.v4.view.ViewPager.OnPageChangeListener;
49import android.util.Log;
50import android.view.KeyEvent;
51import android.view.LayoutInflater;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55import android.view.View;
56import android.view.View.OnClickListener;
57import android.view.ViewGroup;
58import android.widget.AbsListView;
59import android.widget.AbsListView.OnScrollListener;
60import android.widget.CheckBox;
61import android.widget.Toast;
62
63import java.util.ArrayList;
64
65public class ContactDetailActivity extends ContactsActivity {
66    private static final String TAG = "ContactDetailActivity";
67
68    private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag";
69    private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag";
70
71    public static final int FRAGMENT_COUNT = 2;
72
73    private ContactLoader.Result mContactData;
74    private Uri mLookupUri;
75
76    private ContactLoaderFragment mLoaderFragment;
77    private ContactDetailFragment mDetailFragment;
78    private ContactDetailUpdatesFragment mUpdatesFragment;
79
80    private ContactDetailTabCarousel mTabCarousel;
81    private ViewPager mViewPager;
82
83    private ContactDetailFragmentCarousel mFragmentCarousel;
84
85    private ViewGroup mRootView;
86    private ViewGroup mContentView;
87    private LayoutInflater mInflater;
88
89    private Handler mHandler = new Handler();
90
91    /**
92     * Whether or not the contact has updates, which dictates whether the
93     * {@link ContactDetailUpdatesFragment} will be shown.
94     */
95    private boolean mContactHasUpdates;
96
97    @Override
98    public void onCreate(Bundle savedState) {
99        super.onCreate(savedState);
100        if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
101            // This activity must not be shown. We have to select the contact in the
102            // PeopleActivity instead ==> Create a forward intent and finish
103            final Intent originalIntent = getIntent();
104            Intent intent = new Intent();
105            intent.setAction(originalIntent.getAction());
106            intent.setDataAndType(originalIntent.getData(), originalIntent.getType());
107            intent.setFlags(
108                    Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_FORWARD_RESULT
109                            | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
110
111            intent.setClass(this, PeopleActivity.class);
112            startActivity(intent);
113            finish();
114            return;
115        }
116
117        setContentView(R.layout.contact_detail_activity);
118        mRootView = (ViewGroup) findViewById(R.id.contact_detail_view);
119        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
120
121        // Manually remove any {@link ViewPager} fragments if there was an orientation change
122        // because the {@link ViewPager} is not used in both orientations. (If we leave the
123        // fragments around, they'll be around in the {@link FragmentManager} but won't be visible
124        // on screen and the {@link ViewPager} won't ask to initialize them again).
125        if (savedState != null) {
126            String aboutFragmentTag = savedState.getString(KEY_DETAIL_FRAGMENT_TAG);
127            String updatesFragmentTag = savedState.getString(KEY_UPDATES_FRAGMENT_TAG);
128
129            FragmentManager fragmentManager = getFragmentManager();
130            mDetailFragment = (ContactDetailFragment) fragmentManager.findFragmentByTag(
131                    aboutFragmentTag);
132            mUpdatesFragment = (ContactDetailUpdatesFragment) fragmentManager.findFragmentByTag(
133                    updatesFragmentTag);
134
135            if (mDetailFragment != null && mUpdatesFragment != null) {
136                FragmentTransaction ft = fragmentManager.beginTransaction();
137                ft.remove(mDetailFragment);
138                ft.remove(mUpdatesFragment);
139                ft.commit();
140            }
141        }
142
143        ActionBar actionBar =  getActionBar();
144        if (actionBar != null) {
145            actionBar.setDisplayHomeAsUpEnabled(true);
146        }
147
148        Log.i(TAG, getIntent().getData().toString());
149    }
150
151    @Override
152    public void onAttachFragment(Fragment fragment) {
153        if (fragment instanceof ContactDetailFragment) {
154            mDetailFragment = (ContactDetailFragment) fragment;
155            mDetailFragment.setListener(mFragmentListener);
156            mDetailFragment.setVerticalScrollListener(mVerticalScrollListener);
157            mDetailFragment.setData(mLookupUri, mContactData);
158            // If the contact has social updates, then the photo should be shown in the tab
159            // carousel, so don't show the photo again in the scrolling list of contact details.
160            // We also don't want to show the photo if there is a fragment carousel because then
161            // the picture will already be on the left of the list of contact details.
162            mDetailFragment.setShowPhotoInHeader(!mContactHasUpdates && mFragmentCarousel == null);
163        } else if (fragment instanceof ContactDetailUpdatesFragment) {
164            mUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
165            mUpdatesFragment.setData(mLookupUri, mContactData);
166        } else if (fragment instanceof ContactLoaderFragment) {
167            mLoaderFragment = (ContactLoaderFragment) fragment;
168            mLoaderFragment.setListener(mLoaderFragmentListener);
169            mLoaderFragment.loadUri(getIntent().getData());
170        }
171    }
172
173    @Override
174    public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
175            boolean globalSearch) {
176        if (globalSearch) {
177            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
178        } else {
179            ContactsSearchManager.startSearch(this, initialQuery);
180        }
181    }
182
183    @Override
184    public boolean onCreateOptionsMenu(Menu menu) {
185        super.onCreateOptionsMenu(menu);
186        MenuInflater inflater = getMenuInflater();
187        inflater.inflate(R.menu.star, menu);
188        return true;
189    }
190
191    @Override
192    public boolean onPrepareOptionsMenu(Menu menu) {
193        MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
194        ViewGroup starredContainer = (ViewGroup) getLayoutInflater().inflate(
195                R.layout.favorites_star, null, false);
196        final CheckBox starredView = (CheckBox) starredContainer.findViewById(R.id.star);
197        starredView.setOnClickListener(new OnClickListener() {
198            @Override
199            public void onClick(View v) {
200                // Toggle "starred" state
201                // Make sure there is a contact
202                if (mLookupUri != null) {
203                    Intent intent = ContactSaveService.createSetStarredIntent(
204                            ContactDetailActivity.this, mLookupUri, starredView.isChecked());
205                    ContactDetailActivity.this.startService(intent);
206                }
207            }
208        });
209        // If there is contact data, update the starred state
210        if (mContactData != null) {
211            ContactDetailDisplayUtils.setStarred(mContactData, starredView);
212        }
213        starredMenuItem.setActionView(starredContainer);
214        return true;
215    }
216
217    @Override
218    public boolean onKeyDown(int keyCode, KeyEvent event) {
219        FragmentKeyListener mCurrentFragment;
220        switch (getCurrentPage()) {
221            case 0:
222                mCurrentFragment = (FragmentKeyListener) mDetailFragment;
223                break;
224            case 1:
225                mCurrentFragment = (FragmentKeyListener) mUpdatesFragment;
226                break;
227            default:
228                throw new IllegalStateException("Invalid current item for ViewPager");
229        }
230        if (mCurrentFragment.handleKeyDown(keyCode)) return true;
231
232        return super.onKeyDown(keyCode, event);
233    }
234
235    private int getCurrentPage() {
236        // If the contact doesn't have any social updates, there is only 1 page (detail fragment).
237        if (!mContactHasUpdates) {
238            return 0;
239        }
240        // Otherwise find the current page based on the {@link ViewPager} or fragment carousel.
241        if (mViewPager != null) {
242            return mViewPager.getCurrentItem();
243        } else if (mFragmentCarousel != null) {
244            return mFragmentCarousel.getCurrentPage();
245        }
246        throw new IllegalStateException("Can't figure out the currently selected page. If the " +
247                "contact has social updates, there must be a ViewPager or fragment carousel");
248    }
249
250    @Override
251    protected void onSaveInstanceState(Bundle outState) {
252        super.onSaveInstanceState(outState);
253        if (mViewPager != null) {
254            outState.putString(KEY_DETAIL_FRAGMENT_TAG, mDetailFragment.getTag());
255            outState.putString(KEY_UPDATES_FRAGMENT_TAG, mUpdatesFragment.getTag());
256            return;
257        }
258    }
259
260    private final ContactLoaderFragmentListener mLoaderFragmentListener =
261            new ContactLoaderFragmentListener() {
262        @Override
263        public void onDetailsLoaded(final ContactLoader.Result result) {
264            if (result == null) {
265                return;
266            }
267            // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the
268            // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler}
269            // on the main thread to execute later.
270            mHandler.post(new Runnable() {
271                @Override
272                public void run() {
273                    mContactData = result;
274                    mLookupUri = result.getLookupUri();
275                    mContactHasUpdates = result.getSocialSnippet() != null;
276                    invalidateOptionsMenu();
277                    setupTitle();
278                    if (mContactHasUpdates) {
279                        setupContactWithUpdates();
280                    } else {
281                        setupContactWithoutUpdates();
282                    }
283                }
284            });
285        }
286    };
287
288    /**
289     * Setup the activity title and subtitle with contact name and company.
290     */
291    private void setupTitle() {
292        CharSequence displayName = ContactDetailDisplayUtils.getDisplayName(this, mContactData);
293        String company =  ContactDetailDisplayUtils.getCompany(this, mContactData);
294
295        ActionBar actionBar = getActionBar();
296        actionBar.setTitle(displayName);
297        actionBar.setSubtitle(company);
298    }
299
300    private void setupContactWithUpdates() {
301        if (mContentView == null) {
302            mContentView = (ViewGroup) mInflater.inflate(
303                    R.layout.contact_detail_container_with_updates, mRootView, false);
304            mRootView.addView(mContentView);
305        }
306
307        // Narrow width screens have a {@link ViewPager} and {@link ContactDetailTabCarousel}
308        mViewPager = (ViewPager) findViewById(R.id.pager);
309        if (mViewPager != null) {
310            mViewPager.removeAllViews();
311            mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
312            mViewPager.setOnPageChangeListener(mOnPageChangeListener);
313        }
314
315        mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
316        if (mTabCarousel != null) {
317            mTabCarousel.setListener(mTabCarouselListener);
318            mTabCarousel.loadData(mContactData);
319        }
320
321        // Otherwise, wide width screens have a {@link ContactDetailFragmentCarousel}
322        mFragmentCarousel = (ContactDetailFragmentCarousel) findViewById(R.id.fragment_carousel);
323        if (mFragmentCarousel != null) {
324            if (mDetailFragment != null) mFragmentCarousel.setAboutFragment(mDetailFragment);
325            if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment);
326        }
327    }
328
329    private void setupContactWithoutUpdates() {
330        if (mContentView == null) {
331            mContentView = (ViewGroup) mInflater.inflate(
332                    R.layout.contact_detail_container_without_updates, mRootView, false);
333            mRootView.addView(mContentView);
334        }
335    }
336
337    private final ContactDetailFragment.Listener mFragmentListener =
338            new ContactDetailFragment.Listener() {
339        @Override
340        public void onContactNotFound() {
341            finish();
342        }
343
344        @Override
345        public void onEditRequested(Uri contactLookupUri) {
346            startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
347        }
348
349        @Override
350        public void onItemClicked(Intent intent) {
351            try {
352                startActivity(intent);
353            } catch (ActivityNotFoundException e) {
354                Log.e(TAG, "No activity found for intent: " + intent);
355            }
356        }
357
358        @Override
359        public void onDeleteRequested(Uri contactUri) {
360            ContactDeletionInteraction.start(ContactDetailActivity.this, contactUri, true);
361        }
362
363        @Override
364        public void onCreateRawContactRequested(
365                ArrayList<ContentValues> values, Account account) {
366            Toast.makeText(ContactDetailActivity.this, R.string.toast_making_personal_copy,
367                    Toast.LENGTH_LONG).show();
368            Intent serviceIntent = ContactSaveService.createNewRawContactIntent(
369                    ContactDetailActivity.this, values, account,
370                    ContactDetailActivity.class, Intent.ACTION_VIEW);
371            startService(serviceIntent);
372
373        }
374    };
375
376    public class ViewPagerAdapter extends FragmentPagerAdapter{
377
378        public ViewPagerAdapter(FragmentManager fm) {
379            super(fm);
380        }
381
382        @Override
383        public Fragment getItem(int position) {
384            switch (position) {
385                case 0:
386                    return new ContactDetailFragment();
387                case 1:
388                    return new ContactDetailUpdatesFragment();
389            }
390            throw new IllegalStateException("No fragment at position " + position);
391        }
392
393        @Override
394        public int getCount() {
395            return FRAGMENT_COUNT;
396        }
397    }
398
399    private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
400
401        @Override
402        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
403            // The user is horizontally dragging the {@link ViewPager}, so send
404            // these scroll changes to the tab carousel. Ignore these events though if the carousel
405            // is actually controlling the {@link ViewPager} scrolls because it will already be
406            // in the correct position.
407            if (mViewPager.isFakeDragging()) {
408                return;
409            }
410            int x = (int) ((position + positionOffset) *
411                    mTabCarousel.getAllowedHorizontalScrollLength());
412            mTabCarousel.scrollTo(x, 0);
413        }
414
415        @Override
416        public void onPageSelected(int position) {
417            // Since a new page has been selected by the {@link ViewPager},
418            // update the tab selection in the carousel.
419            mTabCarousel.setCurrentTab(position);
420        }
421
422        @Override
423        public void onPageScrollStateChanged(int state) {}
424
425    };
426
427    private ContactDetailTabCarousel.Listener mTabCarouselListener =
428            new ContactDetailTabCarousel.Listener() {
429
430        @Override
431        public void onTouchDown() {
432            // The user just started scrolling the carousel, so begin "fake dragging" the
433            // {@link ViewPager} if it's not already doing so.
434            if (mViewPager.isFakeDragging()) {
435                return;
436            }
437            mViewPager.beginFakeDrag();
438        }
439
440        @Override
441        public void onTouchUp() {
442            // The user just stopped scrolling the carousel, so stop "fake dragging" the
443            // {@link ViewPager} if was doing so before.
444            if (mViewPager.isFakeDragging()) {
445                mViewPager.endFakeDrag();
446            }
447        }
448
449        @Override
450        public void onScrollChanged(int l, int t, int oldl, int oldt) {
451            // The user is scrolling the carousel, so send the scroll deltas to the
452            // {@link ViewPager} so it can move in sync.
453            if (mViewPager.isFakeDragging()) {
454                mViewPager.fakeDragBy(oldl-l);
455            }
456        }
457
458        @Override
459        public void onTabSelected(int position) {
460            // The user selected a tab, so update the {@link ViewPager}
461            mViewPager.setCurrentItem(position);
462        }
463    };
464
465    private OnScrollListener mVerticalScrollListener = new OnScrollListener() {
466
467        @Override
468        public void onScroll(
469                AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
470            if (mTabCarousel == null) {
471                return;
472            }
473            // If the FIRST item is not visible on the screen, then the carousel must be pinned
474            // at the top of the screen.
475            if (firstVisibleItem != 0) {
476                mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength());
477                return;
478            }
479            View topView = view.getChildAt(firstVisibleItem);
480            if (topView == null) {
481                return;
482            }
483            int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
484                    - mTabCarousel.getAllowedVerticalScrollLength());
485            mTabCarousel.setY(amtToScroll);
486        }
487
488        @Override
489        public void onScrollStateChanged(AbsListView view, int scrollState) {}
490
491    };
492
493    /**
494     * This interface should be implemented by {@link Fragment}s within this
495     * activity so that the activity can determine whether the currently
496     * displayed view is handling the key event or not.
497     */
498    public interface FragmentKeyListener {
499        /**
500         * Returns true if the key down event will be handled by the implementing class, or false
501         * otherwise.
502         */
503        public boolean handleKeyDown(int keyCode);
504    }
505
506    @Override
507    public boolean onOptionsItemSelected(MenuItem item) {
508
509        switch (item.getItemId()) {
510            case android.R.id.home:
511                finish();
512                return true;
513            default:
514                break;
515        }
516        return super.onOptionsItemSelected(item);
517    }
518}
519