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