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