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.car.qc.provider; 18 19 import android.content.ContentProvider; 20 import android.content.ContentValues; 21 import android.database.Cursor; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.os.Process; 27 import android.os.StrictMode; 28 import android.util.Log; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 33 import com.android.car.qc.QCItem; 34 35 import java.util.Set; 36 37 /** 38 * Base Quick Controls provider implementation. 39 */ 40 public abstract class BaseQCProvider extends ContentProvider { 41 public static final String METHOD_BIND = "QC_METHOD_BIND"; 42 public static final String METHOD_SUBSCRIBE = "QC_METHOD_SUBSCRIBE"; 43 public static final String METHOD_UNSUBSCRIBE = "QC_METHOD_UNSUBSCRIBE"; 44 public static final String METHOD_DESTROY = "QC_METHOD_DESTROY"; 45 public static final String EXTRA_URI = "QC_EXTRA_URI"; 46 public static final String EXTRA_ITEM = "QC_EXTRA_ITEM"; 47 48 private static final String TAG = "BaseQCProvider"; 49 private static final long QC_ANR_TIMEOUT = 3000L; 50 private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper()); 51 private String mCallbackMethod; 52 private final Runnable mAnr = () -> { 53 Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); 54 Log.e(TAG, "Timed out while handling QC method " + mCallbackMethod); 55 }; 56 57 @Override onCreate()58 public boolean onCreate() { 59 return true; 60 } 61 62 @Override call(String method, String arg, Bundle extras)63 public Bundle call(String method, String arg, Bundle extras) { 64 enforceCallingPermissions(); 65 66 Uri uri = getUriWithoutUserId(validateIncomingUriOrNull( 67 extras.getParcelable(EXTRA_URI))); 68 switch(method) { 69 case METHOD_BIND: 70 QCItem item = handleBind(uri); 71 Bundle b = new Bundle(); 72 b.putParcelable(EXTRA_ITEM, item); 73 return b; 74 case METHOD_SUBSCRIBE: 75 handleSubscribe(uri); 76 break; 77 case METHOD_UNSUBSCRIBE: 78 handleUnsubscribe(uri); 79 break; 80 case METHOD_DESTROY: 81 handleDestroy(uri); 82 break; 83 } 84 return super.call(method, arg, extras); 85 } 86 87 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)88 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 89 String sortOrder) { 90 return null; 91 } 92 93 @Override getType(Uri uri)94 public String getType(Uri uri) { 95 return null; 96 } 97 98 @Override insert(Uri uri, ContentValues values)99 public Uri insert(Uri uri, ContentValues values) { 100 return null; 101 } 102 103 @Override delete(Uri uri, String selection, String[] selectionArgs)104 public int delete(Uri uri, String selection, String[] selectionArgs) { 105 return 0; 106 } 107 108 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)109 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 110 return 0; 111 } 112 113 /** 114 * Method to create and return a {@link QCItem}. 115 * 116 * onBind is expected to return as quickly as possible. Therefore, no network or other IO 117 * will be allowed. Any loading that needs to be done should happen in the background and 118 * should then notify the content resolver of the change when ready to provide the 119 * complete data in onBind. 120 */ 121 @Nullable onBind(@onNull Uri uri)122 protected QCItem onBind(@NonNull Uri uri) { 123 return null; 124 } 125 126 /** 127 * Called to inform an app that an item has been subscribed to. 128 * 129 * Subscribing is a way that a host can notify apps of which QCItems they would like to 130 * receive updates for. The providing apps are expected to keep the content up to date 131 * and notify of change via the content resolver. 132 */ onSubscribed(@onNull Uri uri)133 protected void onSubscribed(@NonNull Uri uri) { 134 } 135 136 /** 137 * Called to inform an app that an item has been unsubscribed from. 138 * 139 * This is used to notify providing apps that a host is no longer listening 140 * to updates, so any background processes and/or listeners should be removed. 141 */ onUnsubscribed(@onNull Uri uri)142 protected void onUnsubscribed(@NonNull Uri uri) { 143 } 144 145 /** 146 * Called to inform an app that an item is being destroyed. 147 * 148 * This is used to notify providing apps that a host is no longer going to use this QCItem 149 * instance, so the relevant elements should be cleaned up. 150 */ onDestroy(@onNull Uri uri)151 protected void onDestroy(@NonNull Uri uri) { 152 } 153 154 /** 155 * Returns a Set of packages that are allowed to call this provider. 156 */ 157 @NonNull getAllowlistedPackages()158 protected abstract Set<String> getAllowlistedPackages(); 159 handleBind(Uri uri)160 private QCItem handleBind(Uri uri) { 161 mCallbackMethod = "handleBind"; 162 MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT); 163 try { 164 return onBindStrict(uri); 165 } finally { 166 MAIN_THREAD_HANDLER.removeCallbacks(mAnr); 167 } 168 } 169 onBindStrict(@onNull Uri uri)170 private QCItem onBindStrict(@NonNull Uri uri) { 171 StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 172 try { 173 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 174 .detectAll() 175 176 // TODO(268275789): Revert back to penaltyDeath and ensure it works in 177 // presubmit 178 .penaltyLog() 179 180 .build()); 181 return onBind(uri); 182 } finally { 183 StrictMode.setThreadPolicy(oldPolicy); 184 } 185 } 186 handleSubscribe(@onNull Uri uri)187 private void handleSubscribe(@NonNull Uri uri) { 188 mCallbackMethod = "handleSubscribe"; 189 MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT); 190 try { 191 onSubscribed(uri); 192 } finally { 193 MAIN_THREAD_HANDLER.removeCallbacks(mAnr); 194 } 195 } 196 handleUnsubscribe(@onNull Uri uri)197 private void handleUnsubscribe(@NonNull Uri uri) { 198 mCallbackMethod = "handleUnsubscribe"; 199 MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT); 200 try { 201 onUnsubscribed(uri); 202 } finally { 203 MAIN_THREAD_HANDLER.removeCallbacks(mAnr); 204 } 205 } 206 handleDestroy(@onNull Uri uri)207 private void handleDestroy(@NonNull Uri uri) { 208 mCallbackMethod = "handleDestroy"; 209 MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT); 210 try { 211 onDestroy(uri); 212 } finally { 213 MAIN_THREAD_HANDLER.removeCallbacks(mAnr); 214 } 215 } 216 validateIncomingUriOrNull(Uri uri)217 private Uri validateIncomingUriOrNull(Uri uri) { 218 if (uri == null) { 219 throw new IllegalArgumentException("Uri cannot be null"); 220 } 221 return validateIncomingUri(uri); 222 } 223 enforceCallingPermissions()224 private void enforceCallingPermissions() { 225 String callingPackage = getCallingPackage(); 226 if (callingPackage == null) { 227 throw new IllegalArgumentException("Calling package cannot be null"); 228 } 229 if (!getAllowlistedPackages().contains(callingPackage)) { 230 throw new SecurityException( 231 String.format("%s is not permitted to access provider: %s", callingPackage, 232 getClass().getName())); 233 } 234 } 235 } 236