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