1 /*
2  * Copyright (C) 2015 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.dialer.database;
18 
19 import android.content.AsyncQueryHandler;
20 import android.content.ContentResolver;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.database.Cursor;
24 import android.database.DatabaseUtils;
25 import android.database.sqlite.SQLiteDatabaseCorruptException;
26 import android.net.Uri;
27 import android.support.annotation.Nullable;
28 import android.telephony.PhoneNumberUtils;
29 import android.text.TextUtils;
30 
31 import com.android.dialer.compat.FilteredNumberCompat;
32 import com.android.dialer.database.FilteredNumberContract.FilteredNumber;
33 import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
34 import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes;
35 
36 public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler {
37     private static final int NO_TOKEN = 0;
38 
FilteredNumberAsyncQueryHandler(ContentResolver cr)39     public FilteredNumberAsyncQueryHandler(ContentResolver cr) {
40         super(cr);
41     }
42 
43     /**
44      * Methods for FilteredNumberAsyncQueryHandler result returns.
45      */
46     private static abstract class Listener {
onQueryComplete(int token, Object cookie, Cursor cursor)47         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
48         }
onInsertComplete(int token, Object cookie, Uri uri)49         protected void onInsertComplete(int token, Object cookie, Uri uri) {
50         }
onUpdateComplete(int token, Object cookie, int result)51         protected void onUpdateComplete(int token, Object cookie, int result) {
52         }
onDeleteComplete(int token, Object cookie, int result)53         protected void onDeleteComplete(int token, Object cookie, int result) {
54         }
55     }
56 
57     public interface OnCheckBlockedListener {
58         /**
59          * Invoked after querying if a number is blocked.
60          * @param id The ID of the row if blocked, null otherwise.
61          */
onCheckComplete(Integer id)62         void onCheckComplete(Integer id);
63     }
64 
65     public interface OnBlockNumberListener {
66         /**
67          * Invoked after inserting a blocked number.
68          * @param uri The uri of the newly created row.
69          */
onBlockComplete(Uri uri)70         void onBlockComplete(Uri uri);
71     }
72 
73     public interface OnUnblockNumberListener {
74         /**
75          * Invoked after removing a blocked number
76          * @param rows The number of rows affected (expected value 1).
77          * @param values The deleted data (used for restoration).
78          */
onUnblockComplete(int rows, ContentValues values)79         void onUnblockComplete(int rows, ContentValues values);
80     }
81 
82     public interface OnHasBlockedNumbersListener {
83         /**
84          * @param hasBlockedNumbers {@code true} if any blocked numbers are stored.
85          *     {@code false} otherwise.
86          */
onHasBlockedNumbers(boolean hasBlockedNumbers)87         void onHasBlockedNumbers(boolean hasBlockedNumbers);
88     }
89 
90     @Override
onQueryComplete(int token, Object cookie, Cursor cursor)91     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
92         if (cookie != null) {
93             ((Listener) cookie).onQueryComplete(token, cookie, cursor);
94         }
95     }
96 
97     @Override
onInsertComplete(int token, Object cookie, Uri uri)98     protected void onInsertComplete(int token, Object cookie, Uri uri) {
99         if (cookie != null) {
100             ((Listener) cookie).onInsertComplete(token, cookie, uri);
101         }
102     }
103 
104     @Override
onUpdateComplete(int token, Object cookie, int result)105     protected void onUpdateComplete(int token, Object cookie, int result) {
106         if (cookie != null) {
107             ((Listener) cookie).onUpdateComplete(token, cookie, result);
108         }
109     }
110 
111     @Override
onDeleteComplete(int token, Object cookie, int result)112     protected void onDeleteComplete(int token, Object cookie, int result) {
113         if (cookie != null) {
114             ((Listener) cookie).onDeleteComplete(token, cookie, result);
115         }
116     }
117 
incrementFilteredCount(Integer id)118     public final void incrementFilteredCount(Integer id) {
119         // No concept of counts with new filtering
120         if (FilteredNumberCompat.useNewFiltering()) {
121             return;
122         }
123         startUpdate(NO_TOKEN, null,
124                 ContentUris.withAppendedId(FilteredNumber.CONTENT_URI_INCREMENT_FILTERED_COUNT, id),
125                 null, null, null);
126     }
127 
hasBlockedNumbers(final OnHasBlockedNumbersListener listener)128     public void hasBlockedNumbers(final OnHasBlockedNumbersListener listener) {
129         startQuery(NO_TOKEN,
130                 new Listener() {
131                     @Override
132                     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
133                         listener.onHasBlockedNumbers(cursor != null && cursor.getCount() > 0);
134                     }
135                 },
136                 FilteredNumberCompat.getContentUri(null),
137                 new String[]{ FilteredNumberCompat.getIdColumnName() },
138                 FilteredNumberCompat.useNewFiltering() ? null : FilteredNumberColumns.TYPE
139                         + "=" + FilteredNumberTypes.BLOCKED_NUMBER,
140                 null,
141                 null);
142     }
143 
144     /**
145      * Check if this number has been blocked.
146      *
147      * @return {@code false} if the number was invalid and couldn't be checked,
148      *     {@code true} otherwise,
149      */
isBlockedNumber( final OnCheckBlockedListener listener, String number, String countryIso)150     public boolean isBlockedNumber(
151             final OnCheckBlockedListener listener, String number, String countryIso) {
152         final String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
153         if (TextUtils.isEmpty(e164Number)) {
154             return false;
155         }
156 
157         startQuery(NO_TOKEN,
158                 new Listener() {
159                     @Override
160                     protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
161                         /*
162                          * In the frameworking blocking, numbers can be blocked in both e164 format
163                          * and not, resulting in multiple rows being returned for this query. For
164                          * example, both '16502530000' and '6502530000' can exist at the same time
165                          * and will be returned by this query.
166                          */
167                         if (cursor == null || cursor.getCount() == 0) {
168                             listener.onCheckComplete(null);
169                             return;
170                         }
171                         cursor.moveToFirst();
172                         // New filtering doesn't have a concept of type
173                         if (!FilteredNumberCompat.useNewFiltering()
174                                 && cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns.TYPE))
175                                 != FilteredNumberTypes.BLOCKED_NUMBER) {
176                             listener.onCheckComplete(null);
177                             return;
178                         }
179                         listener.onCheckComplete(
180                                 cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID)));
181                     }
182                 },
183                 FilteredNumberCompat.getContentUri(null),
184                 FilteredNumberCompat.filter(new String[]{FilteredNumberCompat.getIdColumnName(),
185                         FilteredNumberCompat.getTypeColumnName()}),
186                 FilteredNumberCompat.getE164NumberColumnName() + " = ?",
187                 new String[]{e164Number},
188                 null);
189 
190         return true;
191     }
192 
blockNumber( final OnBlockNumberListener listener, String number, @Nullable String countryIso)193     public void blockNumber(
194             final OnBlockNumberListener listener, String number, @Nullable String countryIso) {
195         blockNumber(listener, null, number, countryIso);
196     }
197 
198     /**
199      * Add a number manually blocked by the user.
200      */
blockNumber( final OnBlockNumberListener listener, @Nullable String normalizedNumber, String number, @Nullable String countryIso)201     public void blockNumber(
202             final OnBlockNumberListener listener,
203             @Nullable String normalizedNumber,
204             String number,
205             @Nullable String countryIso) {
206         blockNumber(listener, FilteredNumberCompat.newBlockNumberContentValues(number,
207                 normalizedNumber, countryIso));
208     }
209 
210     /**
211      * Block a number with specified ContentValues. Can be manually added or a restored row
212      * from performing the 'undo' action after unblocking.
213      */
blockNumber(final OnBlockNumberListener listener, ContentValues values)214     public void blockNumber(final OnBlockNumberListener listener, ContentValues values) {
215         startInsert(NO_TOKEN,
216                 new Listener() {
217                     @Override
218                     public void onInsertComplete(int token, Object cookie, Uri uri) {
219                         if (listener != null ) {
220                             listener.onBlockComplete(uri);
221                         }
222                     }
223                 }, FilteredNumberCompat.getContentUri(null), values);
224     }
225 
226     /**
227      * Unblocks the number with the given id.
228      *
229      * @param listener (optional) The {@link OnUnblockNumberListener} called after the number is
230      * unblocked.
231      * @param id The id of the number to unblock.
232      */
unblock(@ullable final OnUnblockNumberListener listener, Integer id)233     public void unblock(@Nullable final OnUnblockNumberListener listener, Integer id) {
234         if (id == null) {
235             throw new IllegalArgumentException("Null id passed into unblock");
236         }
237         unblock(listener, FilteredNumberCompat.getContentUri(id));
238     }
239 
240     /**
241      * Removes row from database.
242      * @param listener (optional) The {@link OnUnblockNumberListener} called after the number is
243      * unblocked.
244      * @param uri The uri of row to remove, from
245      * {@link FilteredNumberAsyncQueryHandler#blockNumber}.
246      */
unblock(@ullable final OnUnblockNumberListener listener, final Uri uri)247     public void unblock(@Nullable final OnUnblockNumberListener listener, final Uri uri) {
248         startQuery(NO_TOKEN, new Listener() {
249             @Override
250             public void onQueryComplete(int token, Object cookie, Cursor cursor) {
251                 int rowsReturned = cursor == null ? 0 : cursor.getCount();
252                 if (rowsReturned != 1) {
253                     throw new SQLiteDatabaseCorruptException
254                             ("Returned " + rowsReturned + " rows for uri "
255                                     + uri + "where 1 expected.");
256                 }
257                 cursor.moveToFirst();
258                 final ContentValues values = new ContentValues();
259                 DatabaseUtils.cursorRowToContentValues(cursor, values);
260                 values.remove(FilteredNumberCompat.getIdColumnName());
261 
262                 startDelete(NO_TOKEN, new Listener() {
263                     @Override
264                     public void onDeleteComplete(int token, Object cookie, int result) {
265                         if (listener != null) {
266                             listener.onUnblockComplete(result, values);
267                         }
268                     }
269                 }, uri, null, null);
270             }
271         }, uri, null, null, null, null);
272     }
273 }
274