1 /* 2 * Copyright (C) 2017 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.dialer.preferredsim.impl; 18 19 import android.Manifest.permission; 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.pm.PackageManager; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteQueryBuilder; 25 import android.net.Uri; 26 import android.support.annotation.NonNull; 27 import android.support.annotation.Nullable; 28 import android.text.TextUtils; 29 import com.android.dialer.preferredsim.PreferredSimFallbackContract; 30 import com.android.dialer.preferredsim.PreferredSimFallbackContract.PreferredSim; 31 import com.google.common.collect.ImmutableMap; 32 33 /** 34 * Content provider for preferred SIM columns that is only available in ContactsProvider after P. 35 * Only supports {@link PreferredSimFallbackContract#CONTENT_URI} without id. Insert and delete not 36 * supported because there are no current use case. 37 * 38 * @see PreferredSimFallbackContract 39 */ 40 public class PreferredSimFallbackProvider extends ContentProvider { 41 42 private static final String UPDATE_ID_SELECTION = PreferredSim.DATA_ID + " = ?"; 43 44 private static final ImmutableMap<String, String> PROJECTION_MAP = 45 ImmutableMap.of( 46 PreferredSim.DATA_ID, 47 PreferredSim.DATA_ID, 48 PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, 49 PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, 50 PreferredSim.PREFERRED_PHONE_ACCOUNT_ID, 51 PreferredSim.PREFERRED_PHONE_ACCOUNT_ID); 52 53 private PreferredSimDatabaseHelper databaseHelper; 54 55 @Override onCreate()56 public boolean onCreate() { 57 databaseHelper = new PreferredSimDatabaseHelper(getContext()); 58 return true; 59 } 60 61 @Nullable 62 @Override query( @onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)63 public Cursor query( 64 @NonNull Uri uri, 65 @Nullable String[] projection, 66 @Nullable String selection, 67 @Nullable String[] selectionArgs, 68 @Nullable String sortOrder) { 69 checkReadContactsPermission(); 70 SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 71 queryBuilder.setStrict(true); 72 queryBuilder.setProjectionMap(PROJECTION_MAP); 73 queryBuilder.setTables(PreferredSimDatabaseHelper.TABLE); 74 return queryBuilder.query( 75 databaseHelper.getReadableDatabase(), 76 projection, 77 selection, 78 selectionArgs, 79 null, 80 null, 81 sortOrder); 82 } 83 84 @Nullable 85 @Override getType(@onNull Uri uri)86 public String getType(@NonNull Uri uri) { 87 return null; 88 } 89 90 @Nullable 91 @Override insert(@onNull Uri uri, @Nullable ContentValues values)92 public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 93 throw new IllegalArgumentException("Unsupported operation"); 94 } 95 96 /** 97 * A row should only be deleted through {@link android.provider.ContactsContract.Data}. Since 98 * {@link android.provider.ContactsContract.Data#_ID} is AUTOINCREMENT and could not be reused, 99 * rows in this database will simply be orphaned and not cleaned up. To unset preference, update 100 * {@link PreferredSim#PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME} and {@link 101 * PreferredSim#PREFERRED_PHONE_ACCOUNT_ID} to {@code null}. Delete is only allowed from dialer so 102 * simulator can wipe all preference. 103 */ 104 @Override delete( @onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)105 public int delete( 106 @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { 107 checkWriteContactsPermission(); 108 109 if (PreferredSimFallbackContract.CONTENT_URI.equals(uri) 110 && selection == null 111 && selectionArgs == null) { 112 return databaseHelper 113 .getWritableDatabase() 114 .delete(PreferredSimDatabaseHelper.TABLE, null, null); 115 } 116 117 if (!TextUtils.equals(getContext().getPackageName(), getCallingPackage())) { 118 throw new IllegalArgumentException("Unsupported operation"); 119 } 120 121 return databaseHelper 122 .getWritableDatabase() 123 .delete(PreferredSimDatabaseHelper.TABLE, selection, selectionArgs); 124 } 125 126 /** 127 * Data will be inserted if {@link PreferredSim#DATA_ID} does not already exist in the database. 128 * During update the whole row will be replaced. 129 * 130 * @param uri must be {@link PreferredSimFallbackContract#CONTENT_URI} 131 * @param values must contains exactly the keys {@link 132 * PreferredSim#PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME} {@link 133 * PreferredSim#PREFERRED_PHONE_ACCOUNT_ID}. The value may be {@code null} 134 * @param selection must equals "data_id = ?" 135 * @param selectionArgs must contains exactly the {@link PreferredSim#DATA_ID} 136 */ 137 @Override update( @onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)138 public int update( 139 @NonNull Uri uri, 140 @Nullable ContentValues values, 141 @Nullable String selection, 142 @Nullable String[] selectionArgs) { 143 checkWriteContactsPermission(); 144 if (values == null) { 145 return 0; 146 } 147 if (!UPDATE_ID_SELECTION.equals(selection) 148 || selectionArgs == null 149 || selectionArgs.length != 1) { 150 throw new IllegalArgumentException("Unsupported operation"); 151 } 152 values.put(PreferredSim.DATA_ID, selectionArgs[0]); 153 if (databaseHelper.getWritableDatabase().replace(PreferredSimDatabaseHelper.TABLE, null, values) 154 == -1) { 155 throw new IllegalStateException("update failed"); 156 } 157 getContext().getContentResolver().notifyChange(PreferredSimFallbackContract.CONTENT_URI, null); 158 return 1; 159 } 160 checkReadContactsPermission()161 private void checkReadContactsPermission() { 162 if (getContext().checkCallingOrSelfPermission(permission.READ_CONTACTS) 163 == PackageManager.PERMISSION_DENIED) { 164 throw new SecurityException("READ_CONTACTS required"); 165 } 166 } 167 checkWriteContactsPermission()168 private void checkWriteContactsPermission() { 169 if (getContext().checkCallingOrSelfPermission(permission.WRITE_CONTACTS) 170 == PackageManager.PERMISSION_DENIED) { 171 throw new SecurityException("WRITE_CONTACTS required"); 172 } 173 } 174 } 175