1 /*
2  * Copyright (C) 2015 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.messaging.datamodel.data;
18 
19 import android.app.LoaderManager;
20 import android.content.Context;
21 import android.content.Loader;
22 import android.database.Cursor;
23 import android.os.Bundle;
24 
25 import com.android.messaging.datamodel.BoundCursorLoader;
26 import com.android.messaging.datamodel.FrequentContactsCursorBuilder;
27 import com.android.messaging.datamodel.MessagingContentProvider;
28 import com.android.messaging.datamodel.binding.BindableData;
29 import com.android.messaging.datamodel.binding.BindingBase;
30 import com.android.messaging.sms.MmsConfig;
31 import com.android.messaging.util.Assert;
32 import com.android.messaging.util.ContactUtil;
33 import com.android.messaging.util.LogUtil;
34 
35 /**
36  * Class to access phone contacts.
37  * The caller is responsible for ensuring that the app has READ_CONTACTS permission (see
38  * {@link ContactUtil#hasReadContactsPermission()}) before instantiating this class.
39  */
40 public class ContactPickerData extends BindableData implements
41         LoaderManager.LoaderCallbacks<Cursor> {
42     public interface ContactPickerDataListener {
onAllContactsCursorUpdated(Cursor data)43         void onAllContactsCursorUpdated(Cursor data);
onFrequentContactsCursorUpdated(Cursor data)44         void onFrequentContactsCursorUpdated(Cursor data);
onContactCustomColorLoaded(ContactPickerData data)45         void onContactCustomColorLoaded(ContactPickerData data);
46     }
47 
48     private static final String BINDING_ID = "bindingId";
49     private final Context mContext;
50     private LoaderManager mLoaderManager;
51     private ContactPickerDataListener mListener;
52     private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder;
53 
ContactPickerData(final Context context, final ContactPickerDataListener listener)54     public ContactPickerData(final Context context, final ContactPickerDataListener listener) {
55         mListener = listener;
56         mContext = context;
57         mFrequentContactsCursorBuilder = new FrequentContactsCursorBuilder();
58     }
59 
60     private static final int ALL_CONTACTS_LOADER = 1;
61     private static final int FREQUENT_CONTACTS_LOADER = 2;
62     private static final int PARTICIPANT_LOADER = 3;
63 
64     @Override
onCreateLoader(final int id, final Bundle args)65     public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
66         final String bindingId = args.getString(BINDING_ID);
67         // Check if data still bound to the requesting ui element
68         if (isBound(bindingId)) {
69             switch (id) {
70                 case ALL_CONTACTS_LOADER:
71                     return ContactUtil.getPhones(mContext)
72                             .createBoundCursorLoader(bindingId);
73                 case FREQUENT_CONTACTS_LOADER:
74                     return ContactUtil.getFrequentContacts(mContext)
75                             .createBoundCursorLoader(bindingId);
76                 case PARTICIPANT_LOADER:
77                     return new BoundCursorLoader(bindingId, mContext,
78                             MessagingContentProvider.PARTICIPANTS_URI,
79                             ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
80                 default:
81                     Assert.fail("Unknown loader id for contact picker!");
82                     break;
83             }
84         } else {
85             LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the contacts list");
86         }
87         return null;
88     }
89 
90     /**
91      * {@inheritDoc}
92      */
93     @Override
onLoadFinished(final Loader<Cursor> loader, final Cursor data)94     public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
95         final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
96         if (isBound(cursorLoader.getBindingId())) {
97             switch (loader.getId()) {
98                 case ALL_CONTACTS_LOADER:
99                     mListener.onAllContactsCursorUpdated(data);
100                     mFrequentContactsCursorBuilder.setAllContacts(data);
101                     break;
102                 case FREQUENT_CONTACTS_LOADER:
103                     mFrequentContactsCursorBuilder.setFrequents(data);
104                     break;
105                 case PARTICIPANT_LOADER:
106                     mListener.onContactCustomColorLoaded(this);
107                     break;
108                 default:
109                     Assert.fail("Unknown loader id for contact picker!");
110                     break;
111             }
112 
113             if (loader.getId() != PARTICIPANT_LOADER) {
114                 // The frequent contacts cursor to be used in the UI depends on results from both
115                 // all contacts and frequent contacts loader, and we don't know which will finish
116                 // first. Therefore, try to build the cursor and notify the listener if it's
117                 // successfully built.
118                 final Cursor frequentContactsCursor = mFrequentContactsCursorBuilder.build();
119                 if (frequentContactsCursor != null) {
120                     mListener.onFrequentContactsCursorUpdated(frequentContactsCursor);
121                 }
122             }
123         } else {
124             LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the contacts list");
125         }
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
onLoaderReset(final Loader<Cursor> loader)132     public void onLoaderReset(final Loader<Cursor> loader) {
133         final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
134         if (isBound(cursorLoader.getBindingId())) {
135             switch (loader.getId()) {
136                 case ALL_CONTACTS_LOADER:
137                     mListener.onAllContactsCursorUpdated(null);
138                     mFrequentContactsCursorBuilder.setAllContacts(null);
139                     break;
140                 case FREQUENT_CONTACTS_LOADER:
141                     mListener.onFrequentContactsCursorUpdated(null);
142                     mFrequentContactsCursorBuilder.setFrequents(null);
143                     break;
144                 case PARTICIPANT_LOADER:
145                     mListener.onContactCustomColorLoaded(this);
146                     break;
147                 default:
148                     Assert.fail("Unknown loader id for contact picker!");
149                     break;
150             }
151         } else {
152             LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the contacts list");
153         }
154     }
155 
init(final LoaderManager loaderManager, final BindingBase<ContactPickerData> binding)156     public void init(final LoaderManager loaderManager,
157                      final BindingBase<ContactPickerData> binding) {
158         final Bundle args = new Bundle();
159         args.putString(BINDING_ID, binding.getBindingId());
160         mLoaderManager = loaderManager;
161         mLoaderManager.initLoader(ALL_CONTACTS_LOADER, args, this);
162         mLoaderManager.initLoader(FREQUENT_CONTACTS_LOADER, args, this);
163         mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
164     }
165 
166     @Override
unregisterListeners()167     protected void unregisterListeners() {
168         mListener = null;
169 
170 
171         // This could be null if we bind but the caller doesn't init the BindableData
172         if (mLoaderManager != null) {
173             mLoaderManager.destroyLoader(ALL_CONTACTS_LOADER);
174             mLoaderManager.destroyLoader(FREQUENT_CONTACTS_LOADER);
175             mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
176             mLoaderManager = null;
177         }
178         mFrequentContactsCursorBuilder.resetBuilder();
179     }
180 
isTooManyParticipants(final int participantCount)181     public static boolean isTooManyParticipants(final int participantCount) {
182         // When creating a conversation, the conversation will be created using the system's
183         // default SIM, so use the default MmsConfig's recipient limit.
184         return (participantCount > MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
185                 .getRecipientLimit());
186     }
187 
getCanAddMoreParticipants(final int participantCount)188     public static boolean getCanAddMoreParticipants(final int participantCount) {
189         // When creating a conversation, the conversation will be created using the system's
190         // default SIM, so use the default MmsConfig's recipient limit.
191         return (participantCount < MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
192                 .getRecipientLimit());
193     }
194 }
195