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