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