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 package com.android.contacts.list;
17 
18 import android.content.AsyncTaskLoader;
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.database.ContentObserver;
22 import android.database.Cursor;
23 import android.database.MatrixCursor;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.provider.ContactsContract.Directory;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import com.android.contacts.R;
31 import com.android.contacts.compat.DirectoryCompat;
32 
33 /**
34  * A specialized loader for the list of directories, see {@link Directory}.
35  */
36 public class DirectoryListLoader extends AsyncTaskLoader<Cursor> {
37 
38     private static final String TAG = "ContactEntryListAdapter";
39 
40     public static final int SEARCH_MODE_NONE = 0;
41     public static final int SEARCH_MODE_DEFAULT = 1;
42     public static final int SEARCH_MODE_CONTACT_SHORTCUT = 2;
43     public static final int SEARCH_MODE_DATA_SHORTCUT = 3;
44 
45     private static final class DirectoryQuery {
46         public static final String ORDER_BY = Directory._ID;
47 
48         public static final String[] PROJECTION = {
49             Directory._ID,
50             Directory.PACKAGE_NAME,
51             Directory.TYPE_RESOURCE_ID,
52             Directory.DISPLAY_NAME,
53             Directory.PHOTO_SUPPORT,
54         };
55 
56         public static final int ID = 0;
57         public static final int PACKAGE_NAME = 1;
58         public static final int TYPE_RESOURCE_ID = 2;
59         public static final int DISPLAY_NAME = 3;
60         public static final int PHOTO_SUPPORT = 4;
61 
getDirectoryUri(int mode)62         public static Uri getDirectoryUri(int mode) {
63             if (mode == SEARCH_MODE_DATA_SHORTCUT || mode == SEARCH_MODE_CONTACT_SHORTCUT) {
64                 return Directory.CONTENT_URI;
65             } else {
66                 return DirectoryCompat.getContentUri();
67             }
68         }
69     }
70 
71     // This is a virtual column created for a MatrixCursor.
72     public static final String DIRECTORY_TYPE = "directoryType";
73 
74     private static final String[] RESULT_PROJECTION = {
75         Directory._ID,
76         DIRECTORY_TYPE,
77         Directory.DISPLAY_NAME,
78         Directory.PHOTO_SUPPORT,
79     };
80 
81     private final ContentObserver mObserver = new ContentObserver(new Handler()) {
82         @Override
83         public void onChange(boolean selfChange) {
84             forceLoad();
85         }
86     };
87 
88     private int mDirectorySearchMode;
89     private boolean mLocalInvisibleDirectoryEnabled;
90 
91     private MatrixCursor mDefaultDirectoryList;
92 
DirectoryListLoader(Context context)93     public DirectoryListLoader(Context context) {
94         super(context);
95     }
96 
setDirectorySearchMode(int mode)97     public void setDirectorySearchMode(int mode) {
98         mDirectorySearchMode = mode;
99     }
100 
101     /**
102      * A flag that indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
103      * be included in the results.
104      */
setLocalInvisibleDirectoryEnabled(boolean flag)105     public void setLocalInvisibleDirectoryEnabled(boolean flag) {
106         this.mLocalInvisibleDirectoryEnabled = flag;
107     }
108 
109     @Override
onStartLoading()110     protected void onStartLoading() {
111         getContext().getContentResolver().
112                 registerContentObserver(DirectoryQuery.getDirectoryUri(mDirectorySearchMode),
113                         false, mObserver);
114         forceLoad();
115     }
116 
117     @Override
onStopLoading()118     protected void onStopLoading() {
119         getContext().getContentResolver().unregisterContentObserver(mObserver);
120     }
121 
122     @Override
loadInBackground()123     public Cursor loadInBackground() {
124         if (mDirectorySearchMode == SEARCH_MODE_NONE) {
125             return getDefaultDirectories();
126         }
127 
128         MatrixCursor result = new MatrixCursor(RESULT_PROJECTION);
129         Context context = getContext();
130         PackageManager pm = context.getPackageManager();
131         String selection;
132         switch (mDirectorySearchMode) {
133             case SEARCH_MODE_DEFAULT:
134                 selection = null;
135                 break;
136 
137             case SEARCH_MODE_CONTACT_SHORTCUT:
138                 selection = Directory.SHORTCUT_SUPPORT + "=" + Directory.SHORTCUT_SUPPORT_FULL;
139                 break;
140 
141             case SEARCH_MODE_DATA_SHORTCUT:
142                 selection = Directory.SHORTCUT_SUPPORT + " IN ("
143                         + Directory.SHORTCUT_SUPPORT_FULL + ", "
144                         + Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY + ")";
145                 break;
146 
147             default:
148                 throw new RuntimeException(
149                         "Unsupported directory search mode: " + mDirectorySearchMode);
150         }
151         Cursor cursor = null;
152         try {
153             cursor = context.getContentResolver().query(
154                     DirectoryQuery.getDirectoryUri(mDirectorySearchMode),
155                     DirectoryQuery.PROJECTION, selection, null, DirectoryQuery.ORDER_BY);
156 
157             if (cursor == null) {
158                 return result;
159             }
160 
161             while(cursor.moveToNext()) {
162                 long directoryId = cursor.getLong(DirectoryQuery.ID);
163                 if (!mLocalInvisibleDirectoryEnabled
164                         && DirectoryCompat.isInvisibleDirectory(directoryId)) {
165                     continue;
166                 }
167                 String directoryType = null;
168 
169                 String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
170                 int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
171                 if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
172                     try {
173                         directoryType = pm.getResourcesForApplication(packageName)
174                                 .getString(typeResourceId);
175                     } catch (Exception e) {
176                         Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
177                     }
178                 }
179                 String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
180                 int photoSupport = cursor.getInt(DirectoryQuery.PHOTO_SUPPORT);
181                 result.addRow(new Object[]{directoryId, directoryType, displayName, photoSupport});
182             }
183         } catch (RuntimeException e) {
184             Log.w(TAG, "Runtime Exception when querying directory");
185         } finally {
186             if (cursor != null) {
187                 cursor.close();
188             }
189         }
190 
191         return result;
192     }
193 
getDefaultDirectories()194     private Cursor getDefaultDirectories() {
195         if (mDefaultDirectoryList == null) {
196             mDefaultDirectoryList = new MatrixCursor(RESULT_PROJECTION);
197             mDefaultDirectoryList.addRow(new Object[] {
198                     Directory.DEFAULT,
199                     getContext().getString(R.string.contactsList),
200                     null,
201                     null
202             });
203             mDefaultDirectoryList.addRow(new Object[] {
204                     Directory.LOCAL_INVISIBLE,
205                     getContext().getString(R.string.local_invisible_directory),
206                     null,
207                     null
208             });
209         }
210         return mDefaultDirectoryList;
211     }
212 
213     @Override
onReset()214     protected void onReset() {
215         stopLoading();
216     }
217 }
218