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 17 package com.android.contacts.activities; 18 19 import com.android.contacts.ContactLoader; 20 import com.android.contacts.ContactSaveService; 21 import com.android.contacts.ContactsActivity; 22 import com.android.contacts.R; 23 import com.android.contacts.detail.ContactDetailDisplayUtils; 24 import com.android.contacts.detail.ContactDetailFragment; 25 import com.android.contacts.detail.ContactDetailLayoutController; 26 import com.android.contacts.detail.ContactLoaderFragment; 27 import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener; 28 import com.android.contacts.interactions.ContactDeletionInteraction; 29 import com.android.contacts.model.AccountWithDataSet; 30 import com.android.contacts.util.PhoneCapabilityTester; 31 32 import android.app.ActionBar; 33 import android.app.Fragment; 34 import android.content.ActivityNotFoundException; 35 import android.content.ContentValues; 36 import android.content.Intent; 37 import android.net.Uri; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.view.KeyEvent; 43 import android.view.Menu; 44 import android.view.MenuInflater; 45 import android.view.MenuItem; 46 import android.view.MenuItem.OnMenuItemClickListener; 47 import android.view.View; 48 import android.view.accessibility.AccessibilityEvent; 49 import android.view.accessibility.AccessibilityManager; 50 import android.widget.Toast; 51 52 import java.util.ArrayList; 53 54 public class ContactDetailActivity extends ContactsActivity { 55 private static final String TAG = "ContactDetailActivity"; 56 57 /** Shows a toogle button for hiding/showing updates. Don't submit with true */ 58 private static final boolean DEBUG_TRANSITIONS = false; 59 60 private ContactLoader.Result mContactData; 61 private Uri mLookupUri; 62 63 private ContactDetailLayoutController mContactDetailLayoutController; 64 private ContactLoaderFragment mLoaderFragment; 65 66 private Handler mHandler = new Handler(); 67 68 @Override onCreate(Bundle savedState)69 protected void onCreate(Bundle savedState) { 70 super.onCreate(savedState); 71 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 72 // This activity must not be shown. We have to select the contact in the 73 // PeopleActivity instead ==> Create a forward intent and finish 74 final Intent originalIntent = getIntent(); 75 Intent intent = new Intent(); 76 intent.setAction(originalIntent.getAction()); 77 intent.setDataAndType(originalIntent.getData(), originalIntent.getType()); 78 79 // If we are launched from the outside, we should create a new task, because the user 80 // can freely navigate the app (this is different from phones, where only the UP button 81 // kicks the user into the full app) 82 if (shouldUpRecreateTask(intent)) { 83 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | 84 Intent.FLAG_ACTIVITY_TASK_ON_HOME); 85 } else { 86 intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | 87 Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_SINGLE_TOP | 88 Intent.FLAG_ACTIVITY_CLEAR_TOP); 89 } 90 91 intent.setClass(this, PeopleActivity.class); 92 startActivity(intent); 93 finish(); 94 return; 95 } 96 97 setContentView(R.layout.contact_detail_activity); 98 99 mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState, 100 getFragmentManager(), null, findViewById(R.id.contact_detail_container), 101 mContactDetailFragmentListener); 102 103 // We want the UP affordance but no app icon. 104 // Setting HOME_AS_UP, SHOW_TITLE and clearing SHOW_HOME does the trick. 105 ActionBar actionBar = getActionBar(); 106 if (actionBar != null) { 107 actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE, 108 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE 109 | ActionBar.DISPLAY_SHOW_HOME); 110 actionBar.setTitle(""); 111 } 112 113 Log.i(TAG, getIntent().getData().toString()); 114 } 115 116 @Override onAttachFragment(Fragment fragment)117 public void onAttachFragment(Fragment fragment) { 118 if (fragment instanceof ContactLoaderFragment) { 119 mLoaderFragment = (ContactLoaderFragment) fragment; 120 mLoaderFragment.setListener(mLoaderFragmentListener); 121 mLoaderFragment.loadUri(getIntent().getData()); 122 } 123 } 124 125 @Override onCreateOptionsMenu(Menu menu)126 public boolean onCreateOptionsMenu(Menu menu) { 127 super.onCreateOptionsMenu(menu); 128 MenuInflater inflater = getMenuInflater(); 129 inflater.inflate(R.menu.star, menu); 130 if (DEBUG_TRANSITIONS) { 131 final MenuItem toggleSocial = 132 menu.add(mLoaderFragment.getLoadStreamItems() ? "less" : "more"); 133 toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 134 toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() { 135 @Override 136 public boolean onMenuItemClick(MenuItem item) { 137 mLoaderFragment.toggleLoadStreamItems(); 138 invalidateOptionsMenu(); 139 return false; 140 } 141 }); 142 } 143 return true; 144 } 145 146 @Override onPrepareOptionsMenu(Menu menu)147 public boolean onPrepareOptionsMenu(Menu menu) { 148 final MenuItem starredMenuItem = menu.findItem(R.id.menu_star); 149 starredMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() { 150 @Override 151 public boolean onMenuItemClick(MenuItem item) { 152 // Toggle "starred" state 153 // Make sure there is a contact 154 if (mLookupUri != null) { 155 // Read the current starred value from the UI instead of using the last 156 // loaded state. This allows rapid tapping without writing the same 157 // value several times 158 final boolean isStarred = starredMenuItem.isChecked(); 159 160 // To improve responsiveness, swap out the picture (and tag) in the UI already 161 ContactDetailDisplayUtils.configureStarredMenuItem(starredMenuItem, 162 mContactData.isDirectoryEntry(), mContactData.isUserProfile(), 163 !isStarred); 164 165 // Now perform the real save 166 Intent intent = ContactSaveService.createSetStarredIntent( 167 ContactDetailActivity.this, mLookupUri, !isStarred); 168 ContactDetailActivity.this.startService(intent); 169 } 170 return true; 171 } 172 }); 173 // If there is contact data, update the starred state 174 if (mContactData != null) { 175 ContactDetailDisplayUtils.configureStarredMenuItem(starredMenuItem, 176 mContactData.isDirectoryEntry(), mContactData.isUserProfile(), 177 mContactData.getStarred()); 178 } 179 return true; 180 } 181 182 @Override onKeyDown(int keyCode, KeyEvent event)183 public boolean onKeyDown(int keyCode, KeyEvent event) { 184 // First check if the {@link ContactLoaderFragment} can handle the key 185 if (mLoaderFragment != null && mLoaderFragment.handleKeyDown(keyCode)) return true; 186 187 // Otherwise find the correct fragment to handle the event 188 FragmentKeyListener mCurrentFragment = mContactDetailLayoutController.getCurrentPage(); 189 if (mCurrentFragment != null && mCurrentFragment.handleKeyDown(keyCode)) return true; 190 191 // In the last case, give the key event to the superclass. 192 return super.onKeyDown(keyCode, event); 193 } 194 195 @Override onSaveInstanceState(Bundle outState)196 protected void onSaveInstanceState(Bundle outState) { 197 super.onSaveInstanceState(outState); 198 if (mContactDetailLayoutController != null) { 199 mContactDetailLayoutController.onSaveInstanceState(outState); 200 } 201 } 202 203 private final ContactLoaderFragmentListener mLoaderFragmentListener = 204 new ContactLoaderFragmentListener() { 205 @Override 206 public void onContactNotFound() { 207 finish(); 208 } 209 210 @Override 211 public void onDetailsLoaded(final ContactLoader.Result result) { 212 if (result == null) { 213 return; 214 } 215 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 216 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 217 // on the main thread to execute later. 218 mHandler.post(new Runnable() { 219 @Override 220 public void run() { 221 // If the activity is destroyed (or will be destroyed soon), don't update the UI 222 if (isFinishing()) { 223 return; 224 } 225 mContactData = result; 226 mLookupUri = result.getLookupUri(); 227 invalidateOptionsMenu(); 228 setupTitle(); 229 mContactDetailLayoutController.setContactData(mContactData); 230 } 231 }); 232 } 233 234 @Override 235 public void onEditRequested(Uri contactLookupUri) { 236 Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri); 237 intent.putExtra( 238 ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true); 239 // Don't finish the detail activity after launching the editor because when the 240 // editor is done, we will still want to show the updated contact details using 241 // this activity. 242 startActivity(intent); 243 } 244 245 @Override 246 public void onDeleteRequested(Uri contactUri) { 247 ContactDeletionInteraction.start(ContactDetailActivity.this, contactUri, true); 248 } 249 }; 250 251 /** 252 * Setup the activity title and subtitle with contact name and company. 253 */ setupTitle()254 private void setupTitle() { 255 CharSequence displayName = ContactDetailDisplayUtils.getDisplayName(this, mContactData); 256 String company = ContactDetailDisplayUtils.getCompany(this, mContactData); 257 258 ActionBar actionBar = getActionBar(); 259 actionBar.setTitle(displayName); 260 actionBar.setSubtitle(company); 261 262 if (!TextUtils.isEmpty(displayName) && 263 AccessibilityManager.getInstance(this).isEnabled()) { 264 View decorView = getWindow().getDecorView(); 265 decorView.setContentDescription(displayName); 266 decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 267 } 268 } 269 270 private final ContactDetailFragment.Listener mContactDetailFragmentListener = 271 new ContactDetailFragment.Listener() { 272 @Override 273 public void onItemClicked(Intent intent) { 274 if (intent == null) { 275 return; 276 } 277 try { 278 startActivity(intent); 279 } catch (ActivityNotFoundException e) { 280 Log.e(TAG, "No activity found for intent: " + intent); 281 } 282 } 283 284 @Override 285 public void onCreateRawContactRequested( 286 ArrayList<ContentValues> values, AccountWithDataSet account) { 287 Toast.makeText(ContactDetailActivity.this, R.string.toast_making_personal_copy, 288 Toast.LENGTH_LONG).show(); 289 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 290 ContactDetailActivity.this, values, account, 291 ContactDetailActivity.class, Intent.ACTION_VIEW); 292 startService(serviceIntent); 293 294 } 295 }; 296 297 /** 298 * This interface should be implemented by {@link Fragment}s within this 299 * activity so that the activity can determine whether the currently 300 * displayed view is handling the key event or not. 301 */ 302 public interface FragmentKeyListener { 303 /** 304 * Returns true if the key down event will be handled by the implementing class, or false 305 * otherwise. 306 */ handleKeyDown(int keyCode)307 public boolean handleKeyDown(int keyCode); 308 } 309 } 310