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