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