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