1 /*
2  * Copyright (C) 2016 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  */
16 
17 package com.android.cts.contactdirectoryprovider;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.content.ContentProvider;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import android.content.UriMatcher;
26 import android.content.res.AssetFileDescriptor;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.database.MatrixCursor;
30 import android.net.Uri;
31 import android.os.Binder;
32 import android.os.Bundle;
33 import android.os.ParcelFileDescriptor;
34 import android.provider.ContactsContract.Contacts;
35 import android.provider.ContactsContract.Directory;
36 import android.util.Log;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.util.Arrays;
42 import java.util.concurrent.Callable;
43 import java.util.concurrent.TimeUnit;
44 
45 public class DirectoryProvider extends ContentProvider {
46     private static final String TAG = "DirectoryProvider";
47     private static final String CONFIG_NAME = "config";
48     private static final String SET_CUSTOM_PREFIX = "set_prefix";
49     private static final String AUTHORITY = "com.android.cts.contact.directory.provider";
50     // Same as com.android.cts.managedprofile.AccountAuthenticator.ACCOUNT_TYPE
51     private static final String TEST_ACCOUNT_TYPE = "com.android.cts.test";
52     private static final String DEFAULT_DISPLAY_NAME = "Directory";
53     private static final String DEFAULT_CONTACT_NAME = "DirectoryContact";
54 
55     private static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
56     private static final String PRIMARY_THUMBNAIL = "photo/primary_thumbnail";
57     private static final String PRIMARY_PHOTO = "photo/primary_photo";
58     private static final String MANAGED_THUMBNAIL = "photo/managed_thumbnail";
59     private static final String MANAGED_PHOTO = "photo/managed_photo";
60     private static final Uri PRIMARY_THUMBNAIL_URI = Uri.withAppendedPath(AUTHORITY_URI,
61             PRIMARY_THUMBNAIL);
62     private static final Uri PRIMARY_PHOTO_URI = Uri.withAppendedPath(AUTHORITY_URI,
63             PRIMARY_PHOTO);
64     private static final Uri MANAGED_THUMBNAIL_URI = Uri.withAppendedPath(AUTHORITY_URI,
65             MANAGED_THUMBNAIL);
66     private static final Uri MANAGED_PHOTO_URI = Uri.withAppendedPath(AUTHORITY_URI,
67             MANAGED_PHOTO);
68 
69     private static final int GAL_BASE = 0;
70     private static final int GAL_DIRECTORIES = GAL_BASE;
71     private static final int GAL_FILTER = GAL_BASE + 1;
72     private static final int GAL_CONTACT = GAL_BASE + 2;
73     private static final int GAL_CONTACT_WITH_ID = GAL_BASE + 3;
74     private static final int GAL_EMAIL_FILTER = GAL_BASE + 4;
75     private static final int GAL_PHONE_FILTER = GAL_BASE + 5;
76     private static final int GAL_PHONE_LOOKUP = GAL_BASE + 6;
77     private static final int GAL_CALLABLES_FILTER = GAL_BASE + 7;
78     private static final int GAL_EMAIL_LOOKUP = GAL_BASE + 8;
79     private static final int GAL_PRIMARY_THUMBNAIL = GAL_BASE + 9;
80     private static final int GAL_PRIMARY_PHOTO = GAL_BASE + 10;
81     private static final int GAL_MANAGED_THUMBNAIL = GAL_BASE + 11;
82     private static final int GAL_MANAGED_PHOTO = GAL_BASE + 12;
83 
84     private final UriMatcher mURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
85 
86     private SharedPreferences mSharedPrefs;
87 
88     @Override
onCreate()89     public boolean onCreate() {
90         mURIMatcher.addURI(AUTHORITY, "directories", GAL_DIRECTORIES);
91         mURIMatcher.addURI(AUTHORITY, "contacts/filter/*", GAL_FILTER);
92         mURIMatcher.addURI(AUTHORITY, "contacts/lookup/*/entities", GAL_CONTACT);
93         mURIMatcher.addURI(AUTHORITY, "contacts/lookup/*/#/entities", GAL_CONTACT_WITH_ID);
94         mURIMatcher.addURI(AUTHORITY, "data/emails/filter/*", GAL_EMAIL_FILTER);
95         mURIMatcher.addURI(AUTHORITY, "data/phones/filter/*", GAL_PHONE_FILTER);
96         mURIMatcher.addURI(AUTHORITY, "phone_lookup/*", GAL_PHONE_LOOKUP);
97         mURIMatcher.addURI(AUTHORITY, "data/callables/filter/*", GAL_CALLABLES_FILTER);
98         mURIMatcher.addURI(AUTHORITY, "data/emails/lookup/*", GAL_EMAIL_LOOKUP);
99         mURIMatcher.addURI(AUTHORITY, PRIMARY_THUMBNAIL, GAL_PRIMARY_THUMBNAIL);
100         mURIMatcher.addURI(AUTHORITY, PRIMARY_PHOTO, GAL_PRIMARY_PHOTO);
101         mURIMatcher.addURI(AUTHORITY, MANAGED_THUMBNAIL, GAL_MANAGED_THUMBNAIL);
102         mURIMatcher.addURI(AUTHORITY, MANAGED_PHOTO, GAL_MANAGED_PHOTO);
103         mSharedPrefs = getContext().getSharedPreferences(CONFIG_NAME, Context.MODE_PRIVATE);
104         return true;
105     }
106 
107     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)108     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
109             String sortOrder) {
110 
111         final String prefix = getPrefix();
112         final int match = mURIMatcher.match(uri);
113         switch (match) {
114             case GAL_DIRECTORIES: {
115                 final MatrixCursor cursor = new MatrixCursor(projection);
116                 final AccountManager am = getContext().getSystemService(AccountManager.class);
117                 Account[] accounts = am.getAccountsByType(TEST_ACCOUNT_TYPE);
118                 Log.i(TAG, "Query GAL directories account size: " + accounts.length);
119                 if (accounts != null) {
120                     for (Account account : accounts) {
121                         final Object[] row = new Object[projection.length];
122                         for (int i = 0; i < projection.length; i++) {
123                             final String column = projection[i];
124                             if (column.equals(Directory.ACCOUNT_NAME)) {
125                                 row[i] = account.name;
126                             } else if (column.equals(Directory.ACCOUNT_TYPE)) {
127                                 row[i] = TEST_ACCOUNT_TYPE;
128                             } else if (column.equals(Directory.TYPE_RESOURCE_ID)) {
129                                 row[i] = R.string.directory_resource_id;
130                             } else if (column.equals(Directory.DISPLAY_NAME)) {
131                                 row[i] = prefix + DEFAULT_DISPLAY_NAME;
132                             } else if (column.equals(Directory.EXPORT_SUPPORT)) {
133                                 row[i] = Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY;
134                             } else if (column.equals(Directory.SHORTCUT_SUPPORT)) {
135                                 row[i] = Directory.SHORTCUT_SUPPORT_NONE;
136                             }
137                         }
138                         cursor.addRow(row);
139                     }
140                 }
141                 return cursor;
142             }
143             case GAL_FILTER:
144             case GAL_CONTACT:
145             case GAL_CONTACT_WITH_ID:
146             case GAL_EMAIL_FILTER:
147             case GAL_PHONE_FILTER:
148             case GAL_PHONE_LOOKUP:
149             case GAL_CALLABLES_FILTER:
150             case GAL_EMAIL_LOOKUP: {
151                 // This parameter must always exist, and set to the device side app package name.
152                 final String callerPackage = uri.getQueryParameter(
153                         Directory.CALLER_PACKAGE_PARAM_KEY);
154                 if (!"com.android.cts.managedprofile".equals(callerPackage)) {
155                     throw new RuntimeException("Expected caller package name not set: uri="
156                             + uri);
157                 }
158 
159                 // TODO: Add all CTS tests for these APIs
160                 final MatrixCursor cursor = new MatrixCursor(projection);
161                 final Object[] row = new Object[projection.length];
162                 for (int i = 0; i < projection.length; i++) {
163                     String column = projection[i];
164                     if (column.equals(Contacts._ID)) {
165                         row[i] = -1;
166                     } else if (column.equals(Contacts.DISPLAY_NAME)) {
167                         row[i] = prefix + DEFAULT_CONTACT_NAME;
168 
169                     } else if (column.equals(Contacts.PHOTO_THUMBNAIL_URI)) {
170                         row[i] = isManagedProfile() ? MANAGED_THUMBNAIL_URI.toString()
171                                 : PRIMARY_THUMBNAIL_URI.toString();
172                     } else if (column.equals(Contacts.PHOTO_URI)) {
173                         row[i] = isManagedProfile() ? MANAGED_PHOTO_URI.toString()
174                                 : PRIMARY_PHOTO_URI.toString();
175                     } else {
176                         row[i] = null;
177                     }
178                 }
179                 cursor.addRow(row);
180                 return cursor;
181             }
182         }
183         return null;
184     }
185 
186     @Override
getType(Uri uri)187     public String getType(Uri uri) {
188         return null;
189     }
190 
191     @Override
delete(Uri uri, String selection, String[] selectionArgs)192     public int delete(Uri uri, String selection, String[] selectionArgs) {
193         throw new UnsupportedOperationException();
194     }
195 
196     @Override
insert(Uri uri, ContentValues values)197     public Uri insert(Uri uri, ContentValues values) {
198         throw new UnsupportedOperationException();
199     }
200 
201     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)202     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
203         throw new UnsupportedOperationException();
204     }
205 
206     @Override
call(String method, String arg, Bundle extras)207     public Bundle call(String method, String arg, Bundle extras) {
208         // Set custom display name, so primary directory and corp directory will have different
209         // display name
210         if (method.equals(SET_CUSTOM_PREFIX)) {
211             Log.i(TAG, "Set directory name prefix: " + arg);
212             mSharedPrefs.edit().putString(SET_CUSTOM_PREFIX, arg).apply();
213             // Force update the content in CP2
214             final long token = Binder.clearCallingIdentity();
215             getContext().getContentResolver().update(Directory.CONTENT_URI, new ContentValues(),
216                     null, null);
217             Binder.restoreCallingIdentity(token);
218         }
219         return new Bundle();
220     }
221 
222     @Override
openAssetFile(final Uri uri, String mode)223     public AssetFileDescriptor openAssetFile(final Uri uri, String mode) {
224         if (!mode.equals("r")) {
225             throw new IllegalArgumentException("mode must be \"r\"");
226         }
227 
228         final int match = mURIMatcher.match(uri);
229         final int resId;
230         switch (match) {
231             case GAL_PRIMARY_THUMBNAIL:
232                 resId = isManagedProfile() ? 0 : R.raw.primary_thumbnail;
233                 break;
234             case GAL_PRIMARY_PHOTO:
235                 resId = isManagedProfile() ? 0 : R.raw.primary_photo;
236                 break;
237             case GAL_MANAGED_THUMBNAIL:
238                 resId = isManagedProfile() ? R.raw.managed_thumbnail : 0;
239                 break;
240             case GAL_MANAGED_PHOTO:
241                 resId = isManagedProfile() ? R.raw.managed_photo : 0;
242                 break;
243             default:
244                 resId = 0;
245                 break;
246         }
247 
248         return resId == 0 ? null : getContext().getResources().openRawResourceFd(resId);
249     }
250 
getPrefix()251     private String getPrefix() {
252         return mSharedPrefs.getString(SET_CUSTOM_PREFIX, "");
253     }
254 
isManagedProfile()255     private boolean isManagedProfile() {
256         return "Managed".equals(getPrefix());
257     }
258 }
259