/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.providers.contacts.enterprise; import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentUris; import android.content.UriMatcher; import android.database.Cursor; import android.database.CursorWrapper; import android.net.Uri; import android.provider.ContactsContract; import android.provider.MediaStore; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Directory; import android.provider.ContactsContract.PhoneLookup; import android.text.TextUtils; import android.util.Log; import com.android.internal.util.ArrayUtils; import com.android.providers.contacts.ContactsProvider2; /** * Wrap cursor returned from work-side ContactsProvider in order to rewrite values in some colums */ public class EnterpriseContactsCursorWrapper extends CursorWrapper { private static final String TAG = "EnterpriseCursorWrapper"; private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE); private static final UriMatcher sUriMatcher = ContactsProvider2.sUriMatcher; // As some of the columns like PHOTO_URI requires contact id, but original projection may not // have it, so caller may use a work projection instead of original project to make the // query. Hence, we need also to restore the cursor to the origianl projection. private final int[] contactIdIndices; // Derived Fields private final Long mDirectoryId; private final boolean mIsDirectoryRemote; private final String[] originalColumnNames; public EnterpriseContactsCursorWrapper(Cursor cursor, String[] originalColumnNames, int[] contactIdIndices, @Nullable Long directoryId) { super(cursor); this.contactIdIndices = contactIdIndices; this.originalColumnNames = originalColumnNames; this.mDirectoryId = directoryId; this.mIsDirectoryRemote = directoryId != null && Directory.isRemoteDirectoryId(directoryId); } @Override public int getColumnCount() { return originalColumnNames.length; } @Override public String[] getColumnNames() { return originalColumnNames; } @Override public String getString(int columnIndex) { final String result = super.getString(columnIndex); final String columnName = super.getColumnName(columnIndex); final long contactId = super.getLong(contactIdIndices[0]); switch (columnName) { case Contacts.PHOTO_THUMBNAIL_URI: if(mIsDirectoryRemote) { return getRemoteDirectoryFileUri(result); } else { return getCorpThumbnailUri(contactId, getWrappedCursor()); } case Contacts.PHOTO_URI: if(mIsDirectoryRemote) { return getRemoteDirectoryFileUri(result); } else { return getCorpDisplayPhotoUri(contactId, getWrappedCursor()); } case Data.PHOTO_FILE_ID: case Data.PHOTO_ID: return null; case Data.CUSTOM_RINGTONE: String ringtoneUri = super.getString(columnIndex); // TODO: Remove this conditional block once accessing sounds in corp // profile becomes possible. if (ringtoneUri != null && !Uri.parse(ringtoneUri).isPathPrefixMatch( MediaStore.Audio.Media.INTERNAL_CONTENT_URI)) { ringtoneUri = null; } return ringtoneUri; case Contacts.LOOKUP_KEY: final String lookupKey = super.getString(columnIndex); if (TextUtils.isEmpty(lookupKey)) { return null; } else { return Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + lookupKey; } default: return result; } } @Override public int getInt(int column) { return (int) getLong(column); } @Override public long getLong(int column) { long result = super.getLong(column); if (ArrayUtils.contains(contactIdIndices, column)) { return result + Contacts.ENTERPRISE_CONTACT_ID_BASE; } else { final String columnName = getColumnName(column); switch (columnName) { case Data.PHOTO_FILE_ID: case Data.PHOTO_ID: return 0; default: return result; } } } private String getRemoteDirectoryFileUri(final String photoUriString) { if (photoUriString == null) { return null; } // Assume that the authority of photoUri is directoryInfo.authority first // TODO: Validate the authority of photoUri is directoryInfo.authority Uri.Builder builder = Directory.ENTERPRISE_FILE_URI.buildUpon(); builder.appendPath(photoUriString); builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, Long.toString(mDirectoryId)); final String outputUri = builder.build().toString(); if (VERBOSE_LOGGING) { Log.v(TAG, "getCorpDirectoryFileUri: output URI=" + outputUri); } return outputUri; } /** * Generate a photo URI for {@link PhoneLookup#PHOTO_THUMBNAIL_URI}. * * Example: "content://com.android.contacts/contacts_corp/ID/photo" * * {@link ContentProvider#openAssetFile} knows how to fetch from this URI. */ private static String getCorpThumbnailUri(long contactId, Cursor originalCursor) { final int thumbnailUriIndex = originalCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI); final String thumbnailUri = originalCursor.getString(thumbnailUriIndex); if (thumbnailUri == null) { // No thumbnail. Just return null. return null; } final int uriCode = sUriMatcher.match(Uri.parse(thumbnailUri)); if (uriCode == ContactsProvider2.CONTACTS_ID_PHOTO) { return ContentUris.appendId(Contacts.CORP_CONTENT_URI.buildUpon(), contactId) .appendPath(Contacts.Photo.CONTENT_DIRECTORY).build().toString(); } else { Log.e(TAG, "EnterpriseContactsCursorWrapper contains invalid PHOTO_THUMBNAIL_URI"); return null; } } /** * Generate a photo URI for {@link PhoneLookup#PHOTO_URI}. * * Example 1: "content://com.android.contacts/contacts_corp/ID/display_photo" * Example 2: "content://com.android.contacts/contacts_corp/ID/photo" * * {@link ContentProvider#openAssetFile} knows how to fetch from this URI. */ private static String getCorpDisplayPhotoUri(long contactId, Cursor originalCursor) { final int photoUriIndex = originalCursor.getColumnIndex(Contacts.PHOTO_URI); final String photoUri = originalCursor.getString(photoUriIndex); if (photoUri == null) { return null; } final int uriCode = sUriMatcher.match(Uri.parse(photoUri)); if (uriCode == ContactsProvider2.CONTACTS_ID_PHOTO) { return ContentUris.appendId(Contacts.CORP_CONTENT_URI.buildUpon(), contactId) .appendPath(Contacts.Photo.CONTENT_DIRECTORY).build().toString(); } else if (uriCode == ContactsProvider2.CONTACTS_ID_DISPLAY_PHOTO || uriCode == ContactsProvider2.DISPLAY_PHOTO_ID) { return ContentUris.appendId(Contacts.CORP_CONTENT_URI.buildUpon(), contactId) .appendPath(Contacts.Photo.DISPLAY_PHOTO).build().toString(); } else { Log.e(TAG, "EnterpriseContactsCursorWrapper contains invalid PHOTO_URI"); return null; } } }