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.preference; 18 19 import android.app.Activity; 20 import android.app.LoaderManager; 21 import android.content.BroadcastReceiver; 22 import android.content.ContentUris; 23 import android.content.Context; 24 import android.content.CursorLoader; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.Loader; 28 import android.content.res.Resources; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.preference.Preference; 33 import android.preference.PreferenceFragment; 34 import android.provider.BlockedNumberContract; 35 import android.provider.ContactsContract.Contacts; 36 import android.provider.ContactsContract.Profile; 37 import android.support.design.widget.Snackbar; 38 import android.support.v4.content.LocalBroadcastManager; 39 import android.telecom.TelecomManager; 40 import android.telephony.TelephonyManager; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.FrameLayout; 45 46 import com.android.contacts.ContactsUtils; 47 import com.android.contacts.R; 48 import com.android.contacts.SimImportService; 49 import com.android.contacts.compat.TelecomManagerUtil; 50 import com.android.contacts.compat.TelephonyManagerCompat; 51 import com.android.contacts.interactions.ExportDialogFragment; 52 import com.android.contacts.interactions.ImportDialogFragment; 53 import com.android.contacts.list.ContactListFilter; 54 import com.android.contacts.list.ContactListFilterController; 55 import com.android.contacts.logging.ScreenEvent.ScreenType; 56 import com.android.contacts.model.AccountTypeManager; 57 import com.android.contacts.model.account.AccountInfo; 58 import com.android.contacts.model.account.AccountsLoader; 59 import com.android.contacts.util.AccountFilterUtil; 60 import com.android.contacts.util.ImplicitIntentsUtil; 61 import com.android.contactsbind.HelpUtils; 62 63 import java.util.List; 64 65 /** 66 * This fragment shows the preferences for "display options" 67 */ 68 public class DisplayOptionsPreferenceFragment extends PreferenceFragment 69 implements Preference.OnPreferenceClickListener, AccountsLoader.AccountsListener { 70 71 private static final int REQUEST_CODE_CUSTOM_CONTACTS_FILTER = 0; 72 73 private static final String ARG_CONTACTS_AVAILABLE = "are_contacts_available"; 74 private static final String ARG_NEW_LOCAL_PROFILE = "new_local_profile"; 75 76 private static final String KEY_ABOUT = "about"; 77 private static final String KEY_ACCOUNTS = "accounts"; 78 private static final String KEY_DEFAULT_ACCOUNT = "defaultAccount"; 79 private static final String KEY_BLOCKED_NUMBERS = "blockedNumbers"; 80 private static final String KEY_DISPLAY_ORDER = "displayOrder"; 81 private static final String KEY_CUSTOM_CONTACTS_FILTER = "customContactsFilter"; 82 private static final String KEY_IMPORT = "import"; 83 private static final String KEY_EXPORT = "export"; 84 private static final String KEY_MY_INFO = "myInfo"; 85 private static final String KEY_SORT_ORDER = "sortOrder"; 86 private static final String KEY_PHONETIC_NAME_DISPLAY = "phoneticNameDisplay"; 87 88 private static final int LOADER_PROFILE = 0; 89 private static final int LOADER_ACCOUNTS = 1; 90 91 /** 92 * Callbacks for hosts of the {@link DisplayOptionsPreferenceFragment}. 93 */ 94 public interface ProfileListener { 95 /** 96 * Invoked after profile has been loaded. 97 */ 98 void onProfileLoaded(Cursor data); 99 } 100 101 /** 102 * The projections that are used to obtain user profile 103 */ 104 public static class ProfileQuery { 105 /** 106 * Not instantiable. 107 */ 108 private ProfileQuery() {} 109 110 private static final String[] PROFILE_PROJECTION_PRIMARY = new String[] { 111 Contacts._ID, // 0 112 Contacts.DISPLAY_NAME_PRIMARY, // 1 113 Contacts.IS_USER_PROFILE, // 2 114 }; 115 116 private static final String[] PROFILE_PROJECTION_ALTERNATIVE = new String[] { 117 Contacts._ID, // 0 118 Contacts.DISPLAY_NAME_ALTERNATIVE, // 1 119 Contacts.IS_USER_PROFILE, // 2 120 }; 121 122 public static final int CONTACT_ID = 0; 123 public static final int CONTACT_DISPLAY_NAME = 1; 124 public static final int CONTACT_IS_USER_PROFILE = 2; 125 } 126 127 private String mNewLocalProfileExtra; 128 private boolean mAreContactsAvailable; 129 130 private boolean mHasProfile; 131 private long mProfileContactId; 132 133 private Preference mMyInfoPreference; 134 135 private ProfileListener mListener; 136 137 private ViewGroup mRootView; 138 private SaveServiceResultListener mSaveServiceListener; 139 140 private final LoaderManager.LoaderCallbacks<Cursor> mProfileLoaderListener = 141 new LoaderManager.LoaderCallbacks<Cursor>() { 142 143 @Override 144 public CursorLoader onCreateLoader(int id, Bundle args) { 145 final CursorLoader loader = createCursorLoader(getContext()); 146 loader.setUri(Profile.CONTENT_URI); 147 loader.setProjection(getProjection(getContext())); 148 return loader; 149 } 150 151 @Override 152 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 153 if (mListener != null) { 154 mListener.onProfileLoaded(data); 155 } 156 } 157 158 public void onLoaderReset(Loader<Cursor> loader) { 159 } 160 }; 161 162 public static DisplayOptionsPreferenceFragment newInstance(String newLocalProfileExtra, 163 boolean areContactsAvailable) { 164 final DisplayOptionsPreferenceFragment fragment = new DisplayOptionsPreferenceFragment(); 165 final Bundle args = new Bundle(); 166 args.putString(ARG_NEW_LOCAL_PROFILE, newLocalProfileExtra); 167 args.putBoolean(ARG_CONTACTS_AVAILABLE, areContactsAvailable); 168 fragment.setArguments(args); 169 return fragment; 170 } 171 172 @Override 173 public void onAttach(Activity activity) { 174 super.onAttach(activity); 175 try { 176 mListener = (ProfileListener) activity; 177 } catch (ClassCastException e) { 178 throw new ClassCastException(activity.toString() + " must implement ProfileListener"); 179 } 180 } 181 182 @Override 183 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 184 // Wrap the preference view in a FrameLayout so we can show a snackbar 185 mRootView = new FrameLayout(getActivity()); 186 final View list = super.onCreateView(inflater, mRootView, savedInstanceState); 187 mRootView.addView(list); 188 return mRootView; 189 } 190 191 @Override 192 public void onViewCreated(View view, Bundle savedInstanceState) { 193 super.onViewCreated(view, savedInstanceState); 194 195 mSaveServiceListener = new SaveServiceResultListener(); 196 LocalBroadcastManager.getInstance(getActivity()).registerReceiver( 197 mSaveServiceListener, 198 new IntentFilter(SimImportService.BROADCAST_SIM_IMPORT_COMPLETE)); 199 } 200 201 @Override 202 public void onCreate(Bundle savedInstanceState) { 203 super.onCreate(savedInstanceState); 204 205 // Load the preferences from an XML resource 206 addPreferencesFromResource(R.xml.preference_display_options); 207 208 final Bundle args = getArguments(); 209 mNewLocalProfileExtra = args.getString(ARG_NEW_LOCAL_PROFILE); 210 mAreContactsAvailable = args.getBoolean(ARG_CONTACTS_AVAILABLE); 211 212 removeUnsupportedPreferences(); 213 214 mMyInfoPreference = findPreference(KEY_MY_INFO); 215 216 final Preference accountsPreference = findPreference(KEY_ACCOUNTS); 217 accountsPreference.setOnPreferenceClickListener(this); 218 219 final Preference importPreference = findPreference(KEY_IMPORT); 220 importPreference.setOnPreferenceClickListener(this); 221 222 final Preference exportPreference = findPreference(KEY_EXPORT); 223 if (exportPreference != null) { 224 exportPreference.setOnPreferenceClickListener(this); 225 } 226 227 final Preference blockedNumbersPreference = findPreference(KEY_BLOCKED_NUMBERS); 228 if (blockedNumbersPreference != null) { 229 blockedNumbersPreference.setOnPreferenceClickListener(this); 230 } 231 232 final Preference aboutPreference = findPreference(KEY_ABOUT); 233 if (aboutPreference != null) { 234 aboutPreference.setOnPreferenceClickListener(this); 235 } 236 237 final Preference customFilterPreference = findPreference(KEY_CUSTOM_CONTACTS_FILTER); 238 if (customFilterPreference != null) { 239 customFilterPreference.setOnPreferenceClickListener(this); 240 setCustomContactsFilterSummary(); 241 } 242 } 243 244 @Override 245 public void onActivityCreated(Bundle savedInstanceState) { 246 super.onActivityCreated(savedInstanceState); 247 getLoaderManager().initLoader(LOADER_PROFILE, null, mProfileLoaderListener); 248 AccountsLoader.loadAccounts(this, LOADER_ACCOUNTS, AccountTypeManager.writableFilter()); 249 } 250 251 @Override 252 public void onDestroyView() { 253 super.onDestroyView(); 254 LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mSaveServiceListener); 255 mRootView = null; 256 } 257 258 public void updateMyInfoPreference(boolean hasProfile, String displayName, long contactId) { 259 final CharSequence summary = hasProfile ? displayName : getString(R.string.set_up_profile); 260 mMyInfoPreference.setSummary(summary); 261 mHasProfile = hasProfile; 262 mProfileContactId = contactId; 263 mMyInfoPreference.setOnPreferenceClickListener(this); 264 } 265 266 private void removeUnsupportedPreferences() { 267 // Disable sort order for CJK locales where it is not supported 268 final Resources resources = getResources(); 269 if (!resources.getBoolean(R.bool.config_sort_order_user_changeable)) { 270 getPreferenceScreen().removePreference(findPreference(KEY_SORT_ORDER)); 271 } 272 273 if (!resources.getBoolean(R.bool.config_phonetic_name_display_user_changeable)) { 274 getPreferenceScreen().removePreference(findPreference(KEY_PHONETIC_NAME_DISPLAY)); 275 } 276 277 if (HelpUtils.isHelpAndFeedbackAvailable()) { 278 getPreferenceScreen().removePreference(findPreference(KEY_ABOUT)); 279 } 280 281 // Disable display order for CJK locales as well 282 if (!resources.getBoolean(R.bool.config_display_order_user_changeable)) { 283 getPreferenceScreen().removePreference(findPreference(KEY_DISPLAY_ORDER)); 284 } 285 286 final boolean isPhone = TelephonyManagerCompat.isVoiceCapable( 287 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE)); 288 final boolean showBlockedNumbers = isPhone && ContactsUtils.FLAG_N_FEATURE 289 && BlockedNumberContract.canCurrentUserBlockNumbers(getContext()); 290 if (!showBlockedNumbers) { 291 getPreferenceScreen().removePreference(findPreference(KEY_BLOCKED_NUMBERS)); 292 } 293 294 if (!mAreContactsAvailable) { 295 getPreferenceScreen().removePreference(findPreference(KEY_EXPORT)); 296 } 297 } 298 299 @Override 300 public void onAccountsLoaded(List<AccountInfo> accounts) { 301 // Hide accounts preferences if no writable accounts exist 302 final DefaultAccountPreference preference = 303 (DefaultAccountPreference) findPreference(KEY_DEFAULT_ACCOUNT); 304 preference.setAccounts(accounts); 305 } 306 307 @Override 308 public Context getContext() { 309 return getActivity(); 310 } 311 312 private CursorLoader createCursorLoader(Context context) { 313 return new CursorLoader(context) { 314 @Override 315 protected Cursor onLoadInBackground() { 316 try { 317 return super.onLoadInBackground(); 318 } catch (RuntimeException e) { 319 return null; 320 } 321 } 322 }; 323 } 324 325 private String[] getProjection(Context context) { 326 final ContactsPreferences contactsPrefs = new ContactsPreferences(context); 327 final int displayOrder = contactsPrefs.getDisplayOrder(); 328 if (displayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) { 329 return ProfileQuery.PROFILE_PROJECTION_PRIMARY; 330 } 331 return ProfileQuery.PROFILE_PROJECTION_ALTERNATIVE; 332 } 333 334 @Override 335 public boolean onPreferenceClick(Preference p) { 336 final String prefKey = p.getKey(); 337 338 if (KEY_ABOUT.equals(prefKey)) { 339 ((ContactsPreferenceActivity) getActivity()).showAboutFragment(); 340 return true; 341 } else if (KEY_IMPORT.equals(prefKey)) { 342 ImportDialogFragment.show(getFragmentManager()); 343 return true; 344 } else if (KEY_EXPORT.equals(prefKey)) { 345 ExportDialogFragment.show(getFragmentManager(), ContactsPreferenceActivity.class, 346 ExportDialogFragment.EXPORT_MODE_ALL_CONTACTS); 347 return true; 348 } else if (KEY_MY_INFO.equals(prefKey)) { 349 if (mHasProfile) { 350 final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, mProfileContactId); 351 ImplicitIntentsUtil.startQuickContact(getActivity(), uri, ScreenType.ME_CONTACT); 352 } else { 353 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 354 intent.putExtra(mNewLocalProfileExtra, true); 355 ImplicitIntentsUtil.startActivityInApp(getActivity(), intent); 356 } 357 return true; 358 } else if (KEY_ACCOUNTS.equals(prefKey)) { 359 ImplicitIntentsUtil.startActivityOutsideApp(getContext(), 360 ImplicitIntentsUtil.getIntentForAddingAccount()); 361 return true; 362 } else if (KEY_BLOCKED_NUMBERS.equals(prefKey)) { 363 final Intent intent = TelecomManagerUtil.createManageBlockedNumbersIntent( 364 (TelecomManager) getContext().getSystemService(Context.TELECOM_SERVICE)); 365 startActivity(intent); 366 return true; 367 } else if (KEY_CUSTOM_CONTACTS_FILTER.equals(prefKey)) { 368 final ContactListFilter filter = 369 ContactListFilterController.getInstance(getContext()).getFilter(); 370 AccountFilterUtil.startAccountFilterActivityForResult( 371 this, REQUEST_CODE_CUSTOM_CONTACTS_FILTER, filter); 372 } 373 return false; 374 } 375 376 @Override 377 public void onActivityResult(int requestCode, int resultCode, Intent data) { 378 if (requestCode == REQUEST_CODE_CUSTOM_CONTACTS_FILTER 379 && resultCode == Activity.RESULT_OK) { 380 AccountFilterUtil.handleAccountFilterResult( 381 ContactListFilterController.getInstance(getContext()), resultCode, data); 382 setCustomContactsFilterSummary(); 383 } else { 384 super.onActivityResult(requestCode, resultCode, data); 385 } 386 } 387 388 private void setCustomContactsFilterSummary() { 389 final Preference customFilterPreference = findPreference(KEY_CUSTOM_CONTACTS_FILTER); 390 if (customFilterPreference != null) { 391 final ContactListFilter filter = 392 ContactListFilterController.getInstance(getContext()).getPersistedFilter(); 393 if (filter != null) { 394 if (filter.filterType == ContactListFilter.FILTER_TYPE_DEFAULT || 395 filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) { 396 customFilterPreference.setSummary(R.string.list_filter_all_accounts); 397 } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) { 398 customFilterPreference.setSummary(R.string.listCustomView); 399 } else { 400 customFilterPreference.setSummary(null); 401 } 402 } 403 } 404 } 405 406 private class SaveServiceResultListener extends BroadcastReceiver { 407 @Override 408 public void onReceive(Context context, Intent intent) { 409 final long now = System.currentTimeMillis(); 410 final long opStart = intent.getLongExtra( 411 SimImportService.EXTRA_OPERATION_REQUESTED_AT_TIME, now); 412 413 // If it's been over 30 seconds the user is likely in a different context so suppress 414 // the toast message. 415 if (now - opStart > 30*1000) return; 416 417 final int code = intent.getIntExtra(SimImportService.EXTRA_RESULT_CODE, 418 SimImportService.RESULT_UNKNOWN); 419 final int count = intent.getIntExtra(SimImportService.EXTRA_RESULT_COUNT, -1); 420 if (code == SimImportService.RESULT_SUCCESS && count > 0) { 421 Snackbar.make(mRootView, getResources().getQuantityString( 422 R.plurals.sim_import_success_toast_fmt, count, count), 423 Snackbar.LENGTH_LONG).show(); 424 } else if (code == SimImportService.RESULT_FAILURE) { 425 Snackbar.make(mRootView, R.string.sim_import_failed_toast, 426 Snackbar.LENGTH_LONG).show(); 427 } 428 } 429 } 430 } 431 432