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