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