1 /*
2  * Copyright (C) 2018 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.settings.homepage.contextualcards;
18 
19 import android.content.ContentProvider;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.UriMatcher;
23 import android.database.Cursor;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.database.sqlite.SQLiteQueryBuilder;
26 import android.net.Uri;
27 import android.os.Build;
28 import android.os.StrictMode;
29 import android.util.Log;
30 
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.settingslib.utils.ThreadUtils;
34 
35 /**
36  * Provider stores and manages user interaction feedback for homepage contextual cards.
37  */
38 public class CardContentProvider extends ContentProvider {
39 
40     public static final String CARD_AUTHORITY = "com.android.settings.homepage.CardContentProvider";
41 
42     public static final Uri REFRESH_CARD_URI = new Uri.Builder()
43                     .scheme(ContentResolver.SCHEME_CONTENT)
44                     .authority(CardContentProvider.CARD_AUTHORITY)
45                     .appendPath(CardDatabaseHelper.CARD_TABLE)
46                     .build();
47 
48     public static final Uri DELETE_CARD_URI = new Uri.Builder()
49             .scheme(ContentResolver.SCHEME_CONTENT)
50             .authority(CardContentProvider.CARD_AUTHORITY)
51             .appendPath(CardDatabaseHelper.CardColumns.CARD_DISMISSED)
52             .build();
53 
54     private static final String TAG = "CardContentProvider";
55     /** URI matcher for ContentProvider queries. */
56     private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
57     /** URI matcher type for cards table */
58     private static final int MATCH_CARDS = 100;
59 
60     static {
URI_MATCHER.addURI(CARD_AUTHORITY, CardDatabaseHelper.CARD_TABLE, MATCH_CARDS)61         URI_MATCHER.addURI(CARD_AUTHORITY, CardDatabaseHelper.CARD_TABLE, MATCH_CARDS);
62     }
63 
64     private CardDatabaseHelper mDBHelper;
65 
66     @Override
onCreate()67     public boolean onCreate() {
68         mDBHelper = CardDatabaseHelper.getInstance(getContext());
69         return true;
70     }
71 
72     @Override
insert(Uri uri, ContentValues values)73     public Uri insert(Uri uri, ContentValues values) {
74         final ContentValues[] cvs = {values};
75         bulkInsert(uri, cvs);
76         return uri;
77     }
78 
79     @Override
bulkInsert(Uri uri, ContentValues[] values)80     public int bulkInsert(Uri uri, ContentValues[] values) {
81         final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
82         int numInserted = 0;
83         final SQLiteDatabase database = mDBHelper.getWritableDatabase();
84 
85         try {
86             maybeEnableStrictMode();
87 
88             final String table = getTableFromMatch(uri);
89             database.beginTransaction();
90 
91             // Here deletion first is avoiding redundant insertion. According to cl/215350754
92             database.delete(table, null /* whereClause */, null /* whereArgs */);
93             for (ContentValues value : values) {
94                 long ret = database.insert(table, null /* nullColumnHack */, value);
95                 if (ret != -1L) {
96                     numInserted++;
97                 } else {
98                     Log.e(TAG, "The row " + value.getAsString(CardDatabaseHelper.CardColumns.NAME)
99                             + " insertion failed! Please check your data.");
100                 }
101             }
102             database.setTransactionSuccessful();
103             getContext().getContentResolver().notifyChange(uri, null /* observer */);
104         } finally {
105             database.endTransaction();
106             StrictMode.setThreadPolicy(oldPolicy);
107         }
108         return numInserted;
109     }
110 
111     @Override
delete(Uri uri, String selection, String[] selectionArgs)112     public int delete(Uri uri, String selection, String[] selectionArgs) {
113         throw new UnsupportedOperationException("delete operation not supported currently.");
114     }
115 
116     @Override
getType(Uri uri)117     public String getType(Uri uri) {
118         throw new UnsupportedOperationException("getType operation not supported currently.");
119     }
120 
121     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)122     public Cursor query(Uri uri, String[] projection, String selection,
123             String[] selectionArgs, String sortOrder) {
124         final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
125         try {
126             maybeEnableStrictMode();
127 
128             final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
129             final String table = getTableFromMatch(uri);
130             queryBuilder.setTables(table);
131             final SQLiteDatabase database = mDBHelper.getReadableDatabase();
132             final Cursor cursor = queryBuilder.query(database,
133                     projection, selection, selectionArgs, null /* groupBy */, null /* having */,
134                     sortOrder);
135 
136             cursor.setNotificationUri(getContext().getContentResolver(), uri);
137             return cursor;
138         } finally {
139             StrictMode.setThreadPolicy(oldPolicy);
140         }
141     }
142 
143     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)144     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
145         throw new UnsupportedOperationException("update operation not supported currently.");
146     }
147 
148     @VisibleForTesting
maybeEnableStrictMode()149     void maybeEnableStrictMode() {
150         if (Build.IS_DEBUGGABLE && ThreadUtils.isMainThread()) {
151             enableStrictMode();
152         }
153     }
154 
155     @VisibleForTesting
enableStrictMode()156     void enableStrictMode() {
157         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().build());
158     }
159 
160     @VisibleForTesting
getTableFromMatch(Uri uri)161     String getTableFromMatch(Uri uri) {
162         final int match = URI_MATCHER.match(uri);
163         String table;
164         switch (match) {
165             case MATCH_CARDS:
166                 table = CardDatabaseHelper.CARD_TABLE;
167                 break;
168             default:
169                 throw new IllegalArgumentException("Unknown Uri format: " + uri);
170         }
171         return table;
172     }
173 }
174