1/*
2 * Copyright (C) 2011 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.Context;
19import android.content.CursorLoader;
20import android.database.Cursor;
21import android.database.MatrixCursor;
22import android.database.MergeCursor;
23import android.net.Uri;
24import android.os.Bundle;
25import android.provider.ContactsContract.Profile;
26
27import com.google.common.collect.Lists;
28
29import java.util.List;
30
31/**
32 * A loader for use in the default contact list, which will also query for the user's profile
33 * if configured to do so.
34 */
35public class ProfileAndContactsLoader extends CursorLoader {
36
37    private boolean mLoadProfile;
38
39    private String[] mProjection;
40
41    private Uri mExtraUri;
42    private String[] mExtraProjection;
43    private String mExtraSelection;
44    private String[] mExtraSelectionArgs;
45    private boolean mMergeExtraContactsAfterPrimary;
46
47    public ProfileAndContactsLoader(Context context) {
48        super(context);
49    }
50
51    /** Whether to load the profile and merge results in before any other results. */
52    public void setLoadProfile(boolean flag) {
53        mLoadProfile = flag;
54    }
55
56    public void setProjection(String[] projection) {
57        super.setProjection(projection);
58        mProjection = projection;
59    }
60
61    /** Configure an extra query and merge results in before the primary results. */
62    public void setLoadExtraContactsFirst(Uri uri, String[] projection) {
63        mExtraUri = uri;
64        mExtraProjection = projection;
65        mMergeExtraContactsAfterPrimary = false;
66    }
67
68    /** Configure an extra query and merge results in after the primary results. */
69    public void setLoadExtraContactsLast(Uri uri, String[] projection, String selection,
70            String[] selectionArgs) {
71        mExtraUri = uri;
72        mExtraProjection = projection;
73        mExtraSelection = selection;
74        mExtraSelectionArgs = selectionArgs;
75        mMergeExtraContactsAfterPrimary = true;
76    }
77
78    private boolean canLoadExtraContacts() {
79        return mExtraUri != null && mExtraProjection != null;
80    }
81
82    @Override
83    public Cursor loadInBackground() {
84        // First load the profile, if enabled.
85        List<Cursor> cursors = Lists.newArrayList();
86        if (mLoadProfile) {
87            cursors.add(loadProfile());
88        }
89        if (canLoadExtraContacts() && !mMergeExtraContactsAfterPrimary) {
90            cursors.add(loadExtraContacts());
91        }
92        // ContactsCursor.loadInBackground() can return null; MergeCursor
93        // correctly handles null cursors.
94        Cursor cursor = null;
95        try {
96            cursor = super.loadInBackground();
97        } catch (NullPointerException | SecurityException e) {
98            // Ignore NPEs and SecurityExceptions thrown by providers
99        }
100        final Cursor contactsCursor = cursor;
101        cursors.add(contactsCursor);
102        if (canLoadExtraContacts() && mMergeExtraContactsAfterPrimary) {
103            cursors.add(loadExtraContacts());
104        }
105        return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
106            @Override
107            public Bundle getExtras() {
108                // Need to get the extras from the contacts cursor.
109                return contactsCursor == null ? new Bundle() : contactsCursor.getExtras();
110            }
111        };
112    }
113
114    /**
115     * Loads the profile into a MatrixCursor. On failure returns null, which
116     * matches the behavior of CursorLoader.loadInBackground().
117     *
118     * @return MatrixCursor containing profile or null on query failure.
119     */
120    private MatrixCursor loadProfile() {
121        Cursor cursor = getContext().getContentResolver().query(Profile.CONTENT_URI, mProjection,
122                null, null, null);
123        if (cursor == null) {
124            return null;
125        }
126        try {
127            MatrixCursor matrix = new MatrixCursor(mProjection);
128            Object[] row = new Object[mProjection.length];
129            while (cursor.moveToNext()) {
130                for (int i = 0; i < row.length; i++) {
131                    row[i] = cursor.getString(i);
132                }
133                matrix.addRow(row);
134            }
135            return matrix;
136        } finally {
137            cursor.close();
138        }
139    }
140
141    private Cursor loadExtraContacts() {
142        return getContext().getContentResolver().query(
143                mExtraUri, mExtraProjection, mExtraSelection, mExtraSelectionArgs, null);
144    }
145}
146