1 /* 2 * Copyright (C) 2021 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; 18 19 import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause; 20 21 import android.Manifest; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.ContentProvider; 25 import android.content.ContentUris; 26 import android.content.ContentValues; 27 import android.content.Context; 28 import android.content.UriMatcher; 29 import android.database.Cursor; 30 import android.database.sqlite.SQLiteDatabase; 31 import android.database.sqlite.SQLiteOpenHelper; 32 import android.database.sqlite.SQLiteQueryBuilder; 33 import android.net.Uri; 34 import android.os.Binder; 35 import android.os.Process; 36 import android.provider.CallLog; 37 import android.telecom.TelecomManager; 38 import android.text.TextUtils; 39 import android.util.Log; 40 41 42 import com.android.providers.contacts.util.SelectionBuilder; 43 44 import java.util.Objects; 45 46 public class CallComposerLocationProvider extends ContentProvider { 47 private static final String TAG = CallComposerLocationProvider.class.getSimpleName(); 48 private static final String DB_NAME = "call_composer_locations.db"; 49 private static final String TABLE_NAME = "locations"; 50 private static final int VERSION = 1; 51 52 private static final int LOCATION = 1; 53 private static final int LOCATION_ID = 2; 54 55 private static class OpenHelper extends SQLiteOpenHelper { OpenHelper(@ullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version)56 public OpenHelper(@Nullable Context context, @Nullable String name, 57 @Nullable SQLiteDatabase.CursorFactory factory, int version) { 58 super(context, name, factory, version); 59 } 60 61 @Override onCreate(SQLiteDatabase db)62 public void onCreate(SQLiteDatabase db) { 63 db.execSQL("CREATE TABLE " + TABLE_NAME+ " (" + 64 CallLog.Locations._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 65 CallLog.Locations.LATITUDE + " REAL, " + 66 CallLog.Locations.LONGITUDE + " REAL" + 67 ");"); 68 } 69 70 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)71 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 72 // Nothing to do here, still on version 1. 73 } 74 } 75 76 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 77 static { sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "", LOCATION)78 sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "", LOCATION); sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "/#", LOCATION_ID)79 sURIMatcher.addURI(CallLog.Locations.AUTHORITY, "/#", LOCATION_ID); 80 } 81 82 private OpenHelper mOpenHelper; 83 84 @Override onCreate()85 public boolean onCreate() { 86 mOpenHelper = new OpenHelper(getContext(), DB_NAME, null, VERSION); 87 return true; 88 } 89 90 @Nullable 91 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)92 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 93 @Nullable String[] selectionArgs, @Nullable String sortOrder) { 94 enforceAccessRestrictions(); 95 final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 96 qb.setTables(TABLE_NAME); 97 qb.setStrict(true); 98 qb.setStrictGrammar(true); 99 final int match = sURIMatcher.match(uri); 100 101 final SelectionBuilder selectionBuilder = new SelectionBuilder(selection); 102 switch (match) { 103 case LOCATION_ID: { 104 selectionBuilder.addClause(getEqualityClause(CallLog.Locations._ID, 105 parseLocationIdFromUri(uri))); 106 break; 107 } 108 default: 109 throw new IllegalArgumentException("Provided URI is not supported for query."); 110 } 111 112 final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 113 return qb.query(db, projection, selectionBuilder.build(), selectionArgs, null, 114 null, sortOrder, null); 115 } 116 117 @Nullable 118 @Override getType(@onNull Uri uri)119 public String getType(@NonNull Uri uri) { 120 final int match = sURIMatcher.match(uri); 121 switch (match) { 122 case LOCATION_ID: 123 return CallLog.Locations.CONTENT_ITEM_TYPE; 124 case LOCATION: 125 return CallLog.Locations.CONTENT_TYPE; 126 default: 127 return null; 128 } 129 } 130 131 @Nullable 132 @Override insert(@onNull Uri uri, @Nullable ContentValues values)133 public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 134 enforceAccessRestrictions(); 135 long id = mOpenHelper.getWritableDatabase().insert(TABLE_NAME, null, values); 136 return ContentUris.withAppendedId(CallLog.Locations.CONTENT_URI, id); 137 } 138 139 @Override delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)140 public int delete(@NonNull Uri uri, @Nullable String selection, 141 @Nullable String[] selectionArgs) { 142 enforceAccessRestrictions(); 143 final int match = sURIMatcher.match(uri); 144 switch (match) { 145 case LOCATION_ID: 146 long id = parseLocationIdFromUri(uri); 147 return mOpenHelper.getWritableDatabase().delete(TABLE_NAME, 148 CallLog.Locations._ID + " = ?", new String[] {String.valueOf(id)}); 149 case LOCATION: 150 Log.w(TAG, "Deleting entire location table!"); 151 return mOpenHelper.getWritableDatabase().delete(TABLE_NAME, "1", null); 152 default: 153 throw new IllegalArgumentException("delete() on the locations" 154 + " does not support the uri " + uri.toString()); 155 } 156 } 157 158 @Override update(@onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)159 public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, 160 @Nullable String[] selectionArgs) { 161 enforceAccessRestrictions(); 162 throw new UnsupportedOperationException( 163 "Call composer location db does not support updates"); 164 } 165 parseLocationIdFromUri(Uri uri)166 private long parseLocationIdFromUri(Uri uri) { 167 try { 168 return Long.parseLong(uri.getPathSegments().get(0)); 169 } catch (NumberFormatException e) { 170 throw new IllegalArgumentException("Invalid location id in uri: " + uri, e); 171 } 172 } 173 enforceAccessRestrictions()174 private void enforceAccessRestrictions() { 175 int uid = Binder.getCallingUid(); 176 if (uid == Process.SYSTEM_UID || uid == Process.myUid() || uid == Process.PHONE_UID) { 177 return; 178 } 179 String defaultDialerPackageName = getContext().getSystemService(TelecomManager.class) 180 .getDefaultDialerPackage(); 181 if (TextUtils.isEmpty(defaultDialerPackageName)) { 182 throw new SecurityException("Access to call composer locations is only allowed for the" 183 + " default dialer, but the default dialer is unset"); 184 } 185 String[] callingPackageCandidates = getContext().getPackageManager().getPackagesForUid(uid); 186 for (String packageCandidate : callingPackageCandidates) { 187 if (Objects.equals(packageCandidate, defaultDialerPackageName)) { 188 return; 189 } 190 } 191 throw new SecurityException("Access to call composer locations is only allowed for the " 192 + "default dialer: " + defaultDialerPackageName); 193 } 194 } 195