• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.providers.contacts.enterprise;
18 
19 import android.annotation.Nullable;
20 import android.content.ContentProvider;
21 import android.content.ContentUris;
22 import android.content.UriMatcher;
23 import android.database.Cursor;
24 import android.database.CursorWrapper;
25 import android.net.Uri;
26 import android.provider.ContactsContract;
27 import android.provider.MediaStore;
28 import android.provider.ContactsContract.Contacts;
29 import android.provider.ContactsContract.Data;
30 import android.provider.ContactsContract.Directory;
31 import android.provider.ContactsContract.PhoneLookup;
32 import android.text.TextUtils;
33 import android.util.Log;
34 
35 import com.android.internal.util.ArrayUtils;
36 import com.android.providers.contacts.ContactsProvider2;
37 
38 /**
39  * Wrap cursor returned from work-side ContactsProvider in order to rewrite values in some colums
40  */
41 public class EnterpriseContactsCursorWrapper extends CursorWrapper {
42 
43     private static final String TAG = "EnterpriseCursorWrapper";
44     private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
45 
46     private static final UriMatcher sUriMatcher = ContactsProvider2.sUriMatcher;
47 
48     // As some of the columns like PHOTO_URI requires contact id, but original projection may not
49     // have it, so caller may use a work projection instead of original project to make the
50     // query. Hence, we need also to restore the cursor to the origianl projection.
51     private final int[] contactIdIndices;
52 
53     // Derived Fields
54     private final Long mDirectoryId;
55     private final boolean mIsDirectoryRemote;
56     private final String[] originalColumnNames;
57 
EnterpriseContactsCursorWrapper(Cursor cursor, String[] originalColumnNames, int[] contactIdIndices, @Nullable Long directoryId)58     public EnterpriseContactsCursorWrapper(Cursor cursor, String[] originalColumnNames,
59             int[] contactIdIndices, @Nullable Long directoryId) {
60         super(cursor);
61         this.contactIdIndices = contactIdIndices;
62         this.originalColumnNames = originalColumnNames;
63         this.mDirectoryId = directoryId;
64         this.mIsDirectoryRemote = directoryId != null
65                 && Directory.isRemoteDirectoryId(directoryId);
66     }
67 
68     @Override
getColumnCount()69     public int getColumnCount() {
70         return originalColumnNames.length;
71     }
72 
73     @Override
getColumnNames()74     public String[] getColumnNames() {
75         return originalColumnNames;
76     }
77 
78     @Override
getString(int columnIndex)79     public String getString(int columnIndex) {
80         final String result = super.getString(columnIndex);
81         final String columnName = super.getColumnName(columnIndex);
82         final long contactId = super.getLong(contactIdIndices[0]);
83         switch (columnName) {
84             case Contacts.PHOTO_THUMBNAIL_URI:
85                 if(mIsDirectoryRemote) {
86                     return getRemoteDirectoryFileUri(result);
87                 } else {
88                     return getCorpThumbnailUri(contactId, getWrappedCursor());
89                 }
90             case Contacts.PHOTO_URI:
91                 if(mIsDirectoryRemote) {
92                     return getRemoteDirectoryFileUri(result);
93                 } else {
94                     return getCorpDisplayPhotoUri(contactId, getWrappedCursor());
95                 }
96             case Data.PHOTO_FILE_ID:
97             case Data.PHOTO_ID:
98                 return null;
99             case Data.CUSTOM_RINGTONE:
100                 String ringtoneUri = super.getString(columnIndex);
101                 // TODO: Remove this conditional block once accessing sounds in corp
102                 // profile becomes possible.
103                 if (ringtoneUri != null
104                         && !Uri.parse(ringtoneUri).isPathPrefixMatch(
105                                 MediaStore.Audio.Media.INTERNAL_CONTENT_URI)) {
106                     ringtoneUri = null;
107                 }
108                 return ringtoneUri;
109             case Contacts.LOOKUP_KEY:
110                 final String lookupKey = super.getString(columnIndex);
111                 if (TextUtils.isEmpty(lookupKey)) {
112                     return null;
113                 } else {
114                     return Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + lookupKey;
115                 }
116             default:
117                 return result;
118         }
119     }
120 
121     @Override
getInt(int column)122     public int getInt(int column) {
123         return (int) getLong(column);
124     }
125 
126     @Override
getLong(int column)127     public long getLong(int column) {
128         long result = super.getLong(column);
129         if (ArrayUtils.contains(contactIdIndices, column)) {
130             return result + Contacts.ENTERPRISE_CONTACT_ID_BASE;
131         } else {
132             final String columnName = getColumnName(column);
133             switch (columnName) {
134                 case Data.PHOTO_FILE_ID:
135                 case Data.PHOTO_ID:
136                     return 0;
137                 default:
138                     return result;
139             }
140         }
141     }
142 
getRemoteDirectoryFileUri(final String photoUriString)143     private String getRemoteDirectoryFileUri(final String photoUriString) {
144         if (photoUriString == null) {
145             return null;
146         }
147 
148         // Assume that the authority of photoUri is directoryInfo.authority first
149         // TODO: Validate the authority of photoUri is directoryInfo.authority
150         Uri.Builder builder = Directory.ENTERPRISE_FILE_URI.buildUpon();
151         builder.appendPath(photoUriString);
152         builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, Long.toString(mDirectoryId));
153         final String outputUri = builder.build().toString();
154         if (VERBOSE_LOGGING) {
155             Log.v(TAG, "getCorpDirectoryFileUri: output URI=" + outputUri);
156         }
157 
158         return outputUri;
159     }
160 
161     /**
162      * Generate a photo URI for {@link PhoneLookup#PHOTO_THUMBNAIL_URI}.
163      *
164      * Example: "content://com.android.contacts/contacts_corp/ID/photo"
165      *
166      * {@link ContentProvider#openAssetFile} knows how to fetch from this URI.
167      */
getCorpThumbnailUri(long contactId, Cursor originalCursor)168     private static String getCorpThumbnailUri(long contactId, Cursor originalCursor) {
169         final int thumbnailUriIndex = originalCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
170         final String thumbnailUri = originalCursor.getString(thumbnailUriIndex);
171         if (thumbnailUri == null) {
172             // No thumbnail. Just return null.
173             return null;
174         }
175 
176         final int uriCode = sUriMatcher.match(Uri.parse(thumbnailUri));
177         if (uriCode == ContactsProvider2.CONTACTS_ID_PHOTO) {
178             return ContentUris.appendId(Contacts.CORP_CONTENT_URI.buildUpon(), contactId)
179                     .appendPath(Contacts.Photo.CONTENT_DIRECTORY).build().toString();
180         } else {
181             Log.e(TAG, "EnterpriseContactsCursorWrapper contains invalid PHOTO_THUMBNAIL_URI");
182             return null;
183         }
184     }
185 
186     /**
187      * Generate a photo URI for {@link PhoneLookup#PHOTO_URI}.
188      *
189      * Example 1: "content://com.android.contacts/contacts_corp/ID/display_photo"
190      * Example 2: "content://com.android.contacts/contacts_corp/ID/photo"
191      *
192      * {@link ContentProvider#openAssetFile} knows how to fetch from this URI.
193      */
getCorpDisplayPhotoUri(long contactId, Cursor originalCursor)194     private static String getCorpDisplayPhotoUri(long contactId, Cursor originalCursor) {
195         final int photoUriIndex = originalCursor.getColumnIndex(Contacts.PHOTO_URI);
196         final String photoUri = originalCursor.getString(photoUriIndex);
197         if (photoUri == null) {
198             return null;
199         }
200 
201         final int uriCode = sUriMatcher.match(Uri.parse(photoUri));
202         if (uriCode == ContactsProvider2.CONTACTS_ID_PHOTO) {
203             return ContentUris.appendId(Contacts.CORP_CONTENT_URI.buildUpon(), contactId)
204                     .appendPath(Contacts.Photo.CONTENT_DIRECTORY).build().toString();
205         } else if (uriCode == ContactsProvider2.CONTACTS_ID_DISPLAY_PHOTO
206                 || uriCode == ContactsProvider2.DISPLAY_PHOTO_ID) {
207             return ContentUris.appendId(Contacts.CORP_CONTENT_URI.buildUpon(), contactId)
208                     .appendPath(Contacts.Photo.DISPLAY_PHOTO).build().toString();
209         } else {
210             Log.e(TAG, "EnterpriseContactsCursorWrapper contains invalid PHOTO_URI");
211             return null;
212         }
213     }
214 }