Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2008 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 android.content.cts;
     18 
     19 import static androidx.core.util.Preconditions.checkArgument;
     20 import static junit.framework.Assert.assertEquals;
     21 
     22 import android.annotation.NonNull;
     23 import android.annotation.Nullable;
     24 import android.content.ContentProvider;
     25 import android.content.ContentProvider.PipeDataWriter;
     26 import android.content.ContentResolver;
     27 import android.content.ContentUris;
     28 import android.content.ContentValues;
     29 import android.content.Context;
     30 import android.content.UriMatcher;
     31 import android.content.res.AssetFileDescriptor;
     32 import android.database.Cursor;
     33 import android.database.CursorWrapper;
     34 import android.database.SQLException;
     35 import android.database.sqlite.SQLiteDatabase;
     36 import android.database.sqlite.SQLiteOpenHelper;
     37 import android.database.sqlite.SQLiteQueryBuilder;
     38 import android.net.Uri;
     39 import android.os.Bundle;
     40 import android.os.CancellationSignal;
     41 import android.os.ParcelFileDescriptor;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 
     45 import java.io.File;
     46 import java.io.FileNotFoundException;
     47 import java.io.FileOutputStream;
     48 import java.io.IOException;
     49 import java.io.OutputStreamWriter;
     50 import java.io.PrintWriter;
     51 import java.io.UnsupportedEncodingException;
     52 import java.util.HashMap;
     53 
     54 public class MockContentProvider extends ContentProvider
     55         implements PipeDataWriter<String> {
     56 
     57     private static final String DEFAULT_AUTHORITY = "ctstest";
     58     private static final String DEFAULT_DBNAME = "ctstest.db";
     59     private static final int DBVERSION = 2;
     60 
     61     private static final int TESTTABLE1 = 1;
     62     private static final int TESTTABLE1_ID = 2;
     63     private static final int TESTTABLE1_CROSS = 3;
     64     private static final int TESTTABLE2 = 4;
     65     private static final int TESTTABLE2_ID = 5;
     66     private static final int SELF_ID = 6;
     67     private static final int CRASH_ID = 6;
     68 
     69     private static @Nullable Uri sRefreshedUri;
     70     private static boolean sRefreshReturnValue;
     71 
     72     private final String mAuthority;
     73     private final String mDbName;
     74     private final UriMatcher URL_MATCHER;
     75     private HashMap<String, String> CTSDBTABLE1_LIST_PROJECTION_MAP;
     76     private HashMap<String, String> CTSDBTABLE2_LIST_PROJECTION_MAP;
     77 
     78     private SQLiteOpenHelper mOpenHelper;
     79 
     80     private static class DatabaseHelper extends SQLiteOpenHelper {
     81 
     82         DatabaseHelper(Context context, String dbname) {
     83             super(context, dbname, null, DBVERSION);
     84         }
     85 
     86         @Override
     87         public void onCreate(SQLiteDatabase db) {
     88             db.execSQL("CREATE TABLE TestTable1 ("
     89                     + "_id INTEGER PRIMARY KEY, " + "key TEXT, " + "value INTEGER"
     90                     + ");");
     91 
     92             db.execSQL("CREATE TABLE TestTable2 ("
     93                     + "_id INTEGER PRIMARY KEY, " + "key TEXT, " + "value INTEGER"
     94                     + ");");
     95         }
     96 
     97         @Override
     98         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
     99             db.execSQL("DROP TABLE IF EXISTS TestTable1");
    100             db.execSQL("DROP TABLE IF EXISTS TestTable2");
    101             onCreate(db);
    102         }
    103     }
    104 
    105     public MockContentProvider() {
    106         this(DEFAULT_AUTHORITY, DEFAULT_DBNAME);
    107     }
    108 
    109     public MockContentProvider(String authority, String dbName) {
    110         mAuthority = authority;
    111         mDbName = dbName;
    112 
    113         URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    114         URL_MATCHER.addURI(mAuthority, "testtable1", TESTTABLE1);
    115         URL_MATCHER.addURI(mAuthority, "testtable1/#", TESTTABLE1_ID);
    116         URL_MATCHER.addURI(mAuthority, "testtable1/cross", TESTTABLE1_CROSS);
    117         URL_MATCHER.addURI(mAuthority, "testtable2", TESTTABLE2);
    118         URL_MATCHER.addURI(mAuthority, "testtable2/#", TESTTABLE2_ID);
    119         URL_MATCHER.addURI(mAuthority, "self", SELF_ID);
    120         URL_MATCHER.addURI(mAuthority, "crash", CRASH_ID);
    121 
    122         CTSDBTABLE1_LIST_PROJECTION_MAP = new HashMap<>();
    123         CTSDBTABLE1_LIST_PROJECTION_MAP.put("_id", "_id");
    124         CTSDBTABLE1_LIST_PROJECTION_MAP.put("key", "key");
    125         CTSDBTABLE1_LIST_PROJECTION_MAP.put("value", "value");
    126 
    127         CTSDBTABLE2_LIST_PROJECTION_MAP = new HashMap<>();
    128         CTSDBTABLE2_LIST_PROJECTION_MAP.put("_id", "_id");
    129         CTSDBTABLE2_LIST_PROJECTION_MAP.put("key", "key");
    130         CTSDBTABLE2_LIST_PROJECTION_MAP.put("value", "value");
    131     }
    132 
    133     @Override
    134     public boolean onCreate() {
    135         mOpenHelper = new DatabaseHelper(getContext(), mDbName);
    136         crashOnLaunchIfNeeded();
    137         return true;
    138     }
    139 
    140     @Override
    141     public int delete(Uri uri, String selection, String[] selectionArgs) {
    142         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    143         String segment;
    144         int count;
    145 
    146         switch (URL_MATCHER.match(uri)) {
    147         case TESTTABLE1:
    148             if (null == selection) {
    149                 // get the count when remove all rows
    150                 selection = "1";
    151             }
    152             count = db.delete("TestTable1", selection, selectionArgs);
    153             break;
    154         case TESTTABLE1_ID:
    155             segment = uri.getPathSegments().get(1);
    156             count = db.delete("TestTable1", "_id=" + segment +
    157                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
    158                     selectionArgs);
    159             break;
    160         case TESTTABLE2:
    161             count = db.delete("TestTable2", selection, selectionArgs);
    162             break;
    163         case TESTTABLE2_ID:
    164             segment = uri.getPathSegments().get(1);
    165             count = db.delete("TestTable2", "_id=" + segment +
    166                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
    167                     selectionArgs);
    168             break;
    169         case SELF_ID:
    170             // Wha...?  Delete ME?!?  O.K.!
    171             Log.i("MockContentProvider", "Delete self requested!");
    172             count = 1;
    173             android.os.Process.killProcess(android.os.Process.myPid());
    174             break;
    175         default:
    176             throw new IllegalArgumentException("Unknown URL " + uri);
    177         }
    178 
    179         getContext().getContentResolver().notifyChange(uri, null);
    180         return count;
    181     }
    182 
    183     @Override
    184     public String getType(Uri uri) {
    185         switch (URL_MATCHER.match(uri)) {
    186         case TESTTABLE1:
    187             return "vnd.android.cursor.dir/com.android.content.testtable1";
    188         case TESTTABLE1_ID:
    189             return "vnd.android.cursor.item/com.android.content.testtable1";
    190         case TESTTABLE1_CROSS:
    191             return "vnd.android.cursor.cross/com.android.content.testtable1";
    192         case TESTTABLE2:
    193             return "vnd.android.cursor.dir/com.android.content.testtable2";
    194         case TESTTABLE2_ID:
    195             return "vnd.android.cursor.item/com.android.content.testtable2";
    196 
    197         default:
    198             throw new IllegalArgumentException("Unknown URL " + uri);
    199         }
    200     }
    201 
    202     @Override
    203     public String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) {
    204         if (URL_MATCHER.match(uri) == TESTTABLE2_ID) {
    205             switch (Integer.parseInt(uri.getPathSegments().get(1)) % 10) {
    206                 case 0:
    207                     return new String[]{"image/jpeg"};
    208                 case 1:
    209                     return new String[]{"audio/mpeg"};
    210                 case 2:
    211                     return new String[]{"video/mpeg", "audio/mpeg"};
    212             }
    213         }
    214         return super.getStreamTypes(uri, mimeTypeFilter);
    215     }
    216 
    217     @Override
    218     public Uri insert(Uri uri, ContentValues initialValues) {
    219         long rowID;
    220         ContentValues values;
    221         String table;
    222         Uri testUri;
    223 
    224         if (initialValues != null)
    225             values = new ContentValues(initialValues);
    226         else
    227             values = new ContentValues();
    228 
    229         if (values.containsKey("value") == false)
    230             values.put("value", -1);
    231 
    232         switch (URL_MATCHER.match(uri)) {
    233         case TESTTABLE1:
    234             table = "TestTable1";
    235             testUri = Uri.parse("content://" + mAuthority + "/testtable1");
    236             break;
    237         case TESTTABLE2:
    238             table = "TestTable2";
    239             testUri = Uri.parse("content://" + mAuthority + "/testtable2");
    240             break;
    241         default:
    242             throw new IllegalArgumentException("Unknown URL " + uri);
    243         }
    244 
    245         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    246         rowID = db.insert(table, "key", values);
    247 
    248         if (rowID > 0) {
    249             Uri url = ContentUris.withAppendedId(testUri, rowID);
    250             getContext().getContentResolver().notifyChange(url, null);
    251             return url;
    252         }
    253 
    254         throw new SQLException("Failed to insert row into " + uri);
    255     }
    256 
    257     @Override
    258     public Cursor query(Uri uri, String[] projection, String selection,
    259             String[] selectionArgs, String sortOrder) {
    260         return query(uri, projection, selection, selectionArgs, sortOrder, null);
    261     }
    262 
    263     @Override
    264     public Cursor query(Uri uri, String[] projection, String selection,
    265             String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
    266 
    267         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    268 
    269         switch (URL_MATCHER.match(uri)) {
    270         case TESTTABLE1:
    271             qb.setTables("TestTable1");
    272             qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
    273             break;
    274 
    275         case TESTTABLE1_ID:
    276             qb.setTables("TestTable1");
    277             qb.appendWhere("_id=" + uri.getPathSegments().get(1));
    278             break;
    279 
    280         case TESTTABLE1_CROSS:
    281             // Create a ridiculous cross-product of the test table.  This is done
    282             // to create an artificially long-running query to enable us to test
    283             // remote query cancellation in ContentResolverTest.
    284             qb.setTables("TestTable1 a, TestTable1 b, TestTable1 c, TestTable1 d, TestTable1 e");
    285             break;
    286 
    287         case TESTTABLE2:
    288             qb.setTables("TestTable2");
    289             qb.setProjectionMap(CTSDBTABLE2_LIST_PROJECTION_MAP);
    290             break;
    291 
    292         case TESTTABLE2_ID:
    293             qb.setTables("TestTable2");
    294             qb.appendWhere("_id=" + uri.getPathSegments().get(1));
    295             break;
    296 
    297         case CRASH_ID:
    298             crashOnLaunchIfNeeded();
    299             qb.setTables("TestTable1");
    300             qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
    301             break;
    302 
    303         default:
    304             throw new IllegalArgumentException("Unknown URL " + uri);
    305         }
    306 
    307         /* If no sort order is specified use the default */
    308         String orderBy = TextUtils.isEmpty(sortOrder) ? "_id" : sortOrder;
    309 
    310         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    311         Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy,
    312                 null, cancellationSignal);
    313 
    314         c.setNotificationUri(getContext().getContentResolver(), uri);
    315         return c;
    316     }
    317 
    318     @Override
    319     public int update(Uri uri, ContentValues values, String selection,
    320             String[] selectionArgs) {
    321         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    322         String segment;
    323         int count;
    324 
    325         switch (URL_MATCHER.match(uri)) {
    326         case TESTTABLE1:
    327             count = db.update("TestTable1", values, selection, selectionArgs);
    328             break;
    329 
    330         case TESTTABLE1_ID:
    331             segment = uri.getPathSegments().get(1);
    332             count = db.update("TestTable1", values, "_id=" + segment +
    333                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
    334                     selectionArgs);
    335             break;
    336 
    337         case TESTTABLE2:
    338             count = db.update("TestTable2", values, selection, selectionArgs);
    339             break;
    340 
    341         case TESTTABLE2_ID:
    342             segment = uri.getPathSegments().get(1);
    343             count = db.update("TestTable2", values, "_id=" + segment +
    344                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
    345                     selectionArgs);
    346             break;
    347 
    348         default:
    349             throw new IllegalArgumentException("Unknown URL " + uri);
    350         }
    351 
    352         getContext().getContentResolver().notifyChange(uri, null);
    353         return count;
    354     }
    355 
    356     @Override
    357     public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
    358         switch (URL_MATCHER.match(uri)) {
    359             case CRASH_ID:
    360                 crashOnLaunchIfNeeded();
    361                 return new AssetFileDescriptor(
    362                         openPipeHelper(uri, null, null,
    363                                 "This is the openAssetFile test data!", this), 0,
    364                         AssetFileDescriptor.UNKNOWN_LENGTH);
    365 
    366             default:
    367                 return super.openAssetFile(uri, mode);
    368         }
    369     }
    370 
    371     @Override
    372     public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
    373             throws FileNotFoundException {
    374         switch (URL_MATCHER.match(uri)) {
    375             case CRASH_ID:
    376                 crashOnLaunchIfNeeded();
    377                 return new AssetFileDescriptor(
    378                         openPipeHelper(uri, null, null,
    379                                 "This is the openTypedAssetFile test data!", this), 0,
    380                         AssetFileDescriptor.UNKNOWN_LENGTH);
    381 
    382             default:
    383                 return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
    384         }
    385     }
    386 
    387     @Override
    388     public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts,
    389             String args) {
    390         FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
    391         PrintWriter pw = null;
    392         try {
    393             pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
    394             pw.print(args);
    395         } catch (UnsupportedEncodingException e) {
    396             Log.w("MockContentProvider", "Ooops", e);
    397         } finally {
    398             if (pw != null) {
    399                 pw.flush();
    400             }
    401             try {
    402                 fout.close();
    403             } catch (IOException e) {
    404             }
    405         }
    406     }
    407 
    408     @Override
    409     public boolean refresh(Uri uri, @Nullable Bundle args,
    410             @Nullable CancellationSignal cancellationSignal) {
    411         sRefreshedUri = uri;
    412         return sRefreshReturnValue;
    413     }
    414 
    415     private void crashOnLaunchIfNeeded() {
    416         if (getCrashOnLaunch(getContext())) {
    417             // The test case wants us to crash our process on first launch.
    418             // Well, okay then!
    419             Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
    420             setCrashOnLaunch(getContext(), false);
    421             android.os.Process.killProcess(android.os.Process.myPid());
    422         }
    423     }
    424 
    425     public static boolean getCrashOnLaunch(Context context) {
    426         File file = getCrashOnLaunchFile(context);
    427         return file.exists();
    428     }
    429 
    430     public static void setCrashOnLaunch(Context context, boolean value) {
    431         File file = getCrashOnLaunchFile(context);
    432         if (value) {
    433             try {
    434                 file.createNewFile();
    435             } catch (IOException ex) {
    436                 throw new RuntimeException("Could not create crash on launch file.", ex);
    437             }
    438         } else {
    439             file.delete();
    440         }
    441     }
    442 
    443     public static void setRefreshReturnValue(boolean value) {
    444         sRefreshReturnValue = value;
    445     }
    446 
    447     public static void assertRefreshed(Uri expectedUri) {
    448         assertEquals(sRefreshedUri, expectedUri);
    449     }
    450 
    451     private static File getCrashOnLaunchFile(Context context) {
    452         return context.getFileStreamPath("MockContentProvider.crashonlaunch");
    453     }
    454 }
    455