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 */
16package com.android.contacts.list;
17
18import android.content.AsyncTaskLoader;
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.database.ContentObserver;
22import android.database.Cursor;
23import android.database.MatrixCursor;
24import android.net.Uri;
25import android.os.Handler;
26import android.provider.ContactsContract.Directory;
27import android.text.TextUtils;
28import android.util.Log;
29
30import com.android.contacts.R;
31import com.android.contacts.compat.DirectoryCompat;
32
33/**
34 * A specialized loader for the list of directories, see {@link Directory}.
35 */
36public 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
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
93    public DirectoryListLoader(Context context) {
94        super(context);
95    }
96
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     */
105    public void setLocalInvisibleDirectoryEnabled(boolean flag) {
106        this.mLocalInvisibleDirectoryEnabled = flag;
107    }
108
109    @Override
110    protected void onStartLoading() {
111        getContext().getContentResolver().
112                registerContentObserver(DirectoryQuery.getDirectoryUri(mDirectorySearchMode),
113                        false, mObserver);
114        forceLoad();
115    }
116
117    @Override
118    protected void onStopLoading() {
119        getContext().getContentResolver().unregisterContentObserver(mObserver);
120    }
121
122    @Override
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
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
214    protected void onReset() {
215        stopLoading();
216    }
217}
218