1 /*
2  * Copyright (C) 2012 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.util;
18 
19 import android.accounts.Account;
20 import android.app.Activity;
21 import android.app.Fragment;
22 import android.content.ActivityNotFoundException;
23 import android.content.AsyncTaskLoader;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.graphics.drawable.Drawable;
29 import android.provider.ContactsContract.Contacts;
30 import android.provider.ContactsContract.Intents;
31 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.widget.Toast;
35 
36 import com.android.contacts.R;
37 import com.android.contacts.activities.ContactEditorActivity;
38 import com.android.contacts.list.AccountFilterActivity;
39 import com.android.contacts.list.ContactListFilter;
40 import com.android.contacts.list.ContactListFilterController;
41 import com.android.contacts.model.AccountTypeManager;
42 import com.android.contacts.model.Contact;
43 import com.android.contacts.model.account.AccountDisplayInfo;
44 import com.android.contacts.model.account.AccountDisplayInfoFactory;
45 import com.android.contacts.model.account.AccountInfo;
46 import com.android.contacts.model.account.AccountType;
47 import com.android.contacts.model.account.AccountWithDataSet;
48 import com.android.contacts.preference.ContactsPreferences;
49 import com.android.contacts.util.concurrent.ContactsExecutors;
50 import com.android.contacts.util.concurrent.ListenableFutureLoader;
51 import com.android.contactsbind.ObjectFactory;
52 
53 import com.google.common.base.Function;
54 import com.google.common.base.Predicate;
55 import com.google.common.collect.Lists;
56 import com.google.common.util.concurrent.Futures;
57 import com.google.common.util.concurrent.ListenableFuture;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 import javax.annotation.Nullable;
63 
64 /**
65  * Utility class for account filter manipulation.
66  */
67 public class AccountFilterUtil {
68     private static final String TAG = AccountFilterUtil.class.getSimpleName();
69 
70      /**
71       * Launches account filter setting Activity using
72       * {@link Fragment#startActivityForResult(Intent, int)}.
73       *
74       * @param requestCode requestCode for {@link Activity#startActivityForResult(Intent, int)}
75       * @param currentFilter currently-selected filter, so that it can be displayed as activated.
76       */
startAccountFilterActivityForResult( Fragment fragment, int requestCode, ContactListFilter currentFilter)77      public static void startAccountFilterActivityForResult(
78              Fragment fragment, int requestCode, ContactListFilter currentFilter) {
79          final Activity activity = fragment.getActivity();
80          if (activity != null) {
81              final Intent intent = new Intent(activity, AccountFilterActivity.class);
82              fragment.startActivityForResult(intent, requestCode);
83          } else {
84              Log.w(TAG, "getActivity() returned null. Ignored");
85          }
86      }
87 
88     /**
89      * Useful method to handle onActivityResult() for
90      * {@link #startAccountFilterActivityForResult(Fragment, int, ContactListFilter)}.
91      *
92      * This will update filter via a given ContactListFilterController.
93      */
handleAccountFilterResult( ContactListFilterController filterController, int resultCode, Intent data)94     public static void handleAccountFilterResult(
95             ContactListFilterController filterController, int resultCode, Intent data) {
96         if (resultCode == Activity.RESULT_OK) {
97             final ContactListFilter filter = (ContactListFilter)
98                     data.getParcelableExtra(AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER);
99             if (filter == null) {
100                 return;
101             }
102             if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
103                 filterController.selectCustomFilter();
104             } else {
105                 filterController.setContactListFilter(filter, /* persistent */
106                         filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
107             }
108         }
109     }
110 
111     /**
112      * Loads a list of contact list filters
113      */
114     public static class FilterLoader extends ListenableFutureLoader<List<ContactListFilter>> {
115         private AccountTypeManager mAccountTypeManager;
116         private DeviceLocalAccountTypeFactory mDeviceLocalFactory;
117 
FilterLoader(Context context)118         public FilterLoader(Context context) {
119             super(context, new IntentFilter(AccountTypeManager.BROADCAST_ACCOUNTS_CHANGED));
120             mAccountTypeManager = AccountTypeManager.getInstance(context);
121             mDeviceLocalFactory = ObjectFactory.getDeviceLocalAccountTypeFactory(context);
122         }
123 
124 
125         @Override
loadData()126         protected ListenableFuture<List<ContactListFilter>> loadData() {
127             return Futures.transform(mAccountTypeManager.filterAccountsAsync(
128                     AccountTypeManager.writableFilter()),
129                     new Function<List<AccountInfo>, List<ContactListFilter>>() {
130                         @Override
131                         public List<ContactListFilter> apply(List<AccountInfo> input) {
132                             return getFiltersForAccounts(input);
133                         }
134                     }, ContactsExecutors.getDefaultThreadPoolExecutor());
135         }
136 
getFiltersForAccounts(List<AccountInfo> accounts)137         private List<ContactListFilter> getFiltersForAccounts(List<AccountInfo> accounts) {
138             final ArrayList<ContactListFilter> accountFilters = new ArrayList<>();
139             AccountInfo.sortAccounts(getDefaultAccount(getContext()), accounts);
140 
141             for (AccountInfo accountInfo : accounts) {
142                 final AccountType accountType = accountInfo.getType();
143                 final AccountWithDataSet account = accountInfo.getAccount();
144                 if ((accountType.isExtension() ||
145                         DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
146                                 mDeviceLocalFactory, account.type)) &&
147                         !account.hasData(getContext())) {
148                     // Hide extensions and device accounts with no raw_contacts.
149                     continue;
150                 }
151                 final Drawable icon = accountType != null ?
152                         accountType.getDisplayIcon(getContext()) : null;
153                 if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
154                         mDeviceLocalFactory, account.type)) {
155                     accountFilters.add(ContactListFilter.createDeviceContactsFilter(icon, account));
156                 } else {
157                     accountFilters.add(ContactListFilter.createAccountFilter(
158                             account.type, account.name, account.dataSet, icon));
159                 }
160             }
161 
162             return accountFilters;
163         }
164     }
165 
166     private static AccountWithDataSet getDefaultAccount(Context context) {
167         return new ContactsPreferences(context).getDefaultAccount();
168     }
169 
170     /**
171      * Returns a {@link ContactListFilter} of type
172      * {@link ContactListFilter#FILTER_TYPE_ALL_ACCOUNTS}, or if a custom "Contacts to display"
173      * filter has been set, then one of type {@link ContactListFilter#FILTER_TYPE_CUSTOM}.
174      */
175     public static ContactListFilter createContactsFilter(Context context) {
176         final int filterType =
177                 ContactListFilterController.getInstance(context).isCustomFilterPersisted()
178                         ? ContactListFilter.FILTER_TYPE_CUSTOM
179                         : ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS;
180         return ContactListFilter.createFilterWithType(filterType);
181     }
182 
183     /**
184      * Start editor intent; and if filter is an account filter, we pass account info to editor so
185      * as to create a contact in that account.
186      */
187     public static void startEditorIntent(Context context, Intent src, ContactListFilter filter) {
188         final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
189         intent.putExtras(src);
190 
191         // If we are in account view, we pass the account explicitly in order to
192         // create contact in the account. This will prevent the default account dialog
193         // from being displayed.
194         if (!isAllContactsFilter(filter) && filter.accountName != null
195                 && filter.accountType != null) {
196             final Account account = new Account(filter.accountName, filter.accountType);
197             intent.putExtra(Intents.Insert.EXTRA_ACCOUNT, account);
198             intent.putExtra(Intents.Insert.EXTRA_DATA_SET, filter.dataSet);
199         } else if (isDeviceContactsFilter(filter)) {
200             intent.putExtra(ContactEditorActivity.EXTRA_ACCOUNT_WITH_DATA_SET,
201                     filter.toAccountWithDataSet());
202         }
203 
204         try {
205             ImplicitIntentsUtil.startActivityInApp(context, intent);
206         } catch (ActivityNotFoundException ex) {
207             Toast.makeText(context, R.string.missing_app, Toast.LENGTH_SHORT).show();
208         }
209     }
210 
211     public static boolean isAllContactsFilter(ContactListFilter filter) {
212         return filter != null && filter.isContactsFilterType();
213     }
214 
215     public static boolean isDeviceContactsFilter(ContactListFilter filter) {
216         return filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS;
217     }
218 
219     /**
220      * Returns action bar title for filter and returns default title "Contacts" if filter is empty.
221      */
222     public static String getActionBarTitleForFilter(Context context, ContactListFilter filter) {
223         if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
224             return context.getString(R.string.account_phone);
225         } else if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT &&
226                 !TextUtils.isEmpty(filter.accountName)) {
227             return getActionBarTitleForAccount(context, filter);
228         }
229         return context.getString(R.string.contactsList);
230     }
231 
232     private static String getActionBarTitleForAccount(Context context, ContactListFilter filter) {
233         final AccountInfo info = AccountTypeManager.getInstance(context)
234                 .getAccountInfoForAccount(filter.toAccountWithDataSet());
235         if (info == null) {
236             return context.getString(R.string.contactsList);
237         }
238 
239         if (info.hasGoogleAccountType()) {
240             return context.getString(R.string.title_from_google);
241         }
242         return context.getString(R.string.title_from_other_accounts,
243                 info.getNameLabel().toString());
244     }
245 }
246