1 /* 2 * Copyright (C) 2016 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.blocking; 18 19 import android.app.FragmentManager; 20 import android.content.ContentUris; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.net.Uri; 25 import android.preference.PreferenceManager; 26 import android.provider.BlockedNumberContract; 27 import android.provider.BlockedNumberContract.BlockedNumbers; 28 import android.support.annotation.Nullable; 29 import android.support.annotation.VisibleForTesting; 30 import android.telecom.TelecomManager; 31 import android.telephony.PhoneNumberUtils; 32 import com.android.dialer.common.LogUtil; 33 import com.android.dialer.configprovider.ConfigProviderComponent; 34 import com.android.dialer.database.FilteredNumberContract.FilteredNumber; 35 import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; 36 import com.android.dialer.database.FilteredNumberContract.FilteredNumberSources; 37 import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; 38 import com.android.dialer.strictmode.StrictModeUtils; 39 import com.android.dialer.telecom.TelecomUtil; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Objects; 43 44 /** 45 * Compatibility class to encapsulate logic to switch between call blocking using {@link 46 * com.android.dialer.database.FilteredNumberContract} and using {@link 47 * android.provider.BlockedNumberContract}. This class should be used rather than explicitly 48 * referencing columns from either contract class in situations where both blocking solutions may be 49 * used. 50 */ 51 @Deprecated 52 public class FilteredNumberCompat { 53 54 private static Boolean canAttemptBlockOperationsForTest; 55 56 @VisibleForTesting 57 public static final String HAS_MIGRATED_TO_NEW_BLOCKING_KEY = "migratedToNewBlocking"; 58 59 /** @return The column name for ID in the filtered number database. */ getIdColumnName(Context context)60 public static String getIdColumnName(Context context) { 61 return useNewFiltering(context) ? BlockedNumbers.COLUMN_ID : FilteredNumberColumns._ID; 62 } 63 64 /** 65 * @return The column name for type in the filtered number database. Will be {@code null} for the 66 * framework blocking implementation. 67 */ 68 @Nullable getTypeColumnName(Context context)69 public static String getTypeColumnName(Context context) { 70 return useNewFiltering(context) ? null : FilteredNumberColumns.TYPE; 71 } 72 73 /** 74 * @return The column name for source in the filtered number database. Will be {@code null} for 75 * the framework blocking implementation 76 */ 77 @Nullable getSourceColumnName(Context context)78 public static String getSourceColumnName(Context context) { 79 return useNewFiltering(context) ? null : FilteredNumberColumns.SOURCE; 80 } 81 82 /** @return The column name for the original number in the filtered number database. */ getOriginalNumberColumnName(Context context)83 public static String getOriginalNumberColumnName(Context context) { 84 return useNewFiltering(context) 85 ? BlockedNumbers.COLUMN_ORIGINAL_NUMBER 86 : FilteredNumberColumns.NUMBER; 87 } 88 89 /** 90 * @return The column name for country iso in the filtered number database. Will be {@code null} 91 * the framework blocking implementation 92 */ 93 @Nullable getCountryIsoColumnName(Context context)94 public static String getCountryIsoColumnName(Context context) { 95 return useNewFiltering(context) ? null : FilteredNumberColumns.COUNTRY_ISO; 96 } 97 98 /** @return The column name for the e164 formatted number in the filtered number database. */ getE164NumberColumnName(Context context)99 public static String getE164NumberColumnName(Context context) { 100 return useNewFiltering(context) 101 ? BlockedNumbers.COLUMN_E164_NUMBER 102 : FilteredNumberColumns.NORMALIZED_NUMBER; 103 } 104 105 /** 106 * @return {@code true} if the current SDK version supports using new filtering, {@code false} 107 * otherwise. 108 */ canUseNewFiltering()109 public static boolean canUseNewFiltering() { 110 return true; 111 } 112 113 /** 114 * @return {@code true} if the new filtering should be used, i.e. it's enabled and any necessary 115 * migration has been performed, {@code false} otherwise. 116 */ useNewFiltering(Context context)117 public static boolean useNewFiltering(Context context) { 118 return !ConfigProviderComponent.get(context) 119 .getConfigProvider() 120 .getBoolean("debug_force_dialer_filtering", false) 121 && canUseNewFiltering() 122 && hasMigratedToNewBlocking(context); 123 } 124 125 /** 126 * @return {@code true} if the user has migrated to use {@link 127 * android.provider.BlockedNumberContract} blocking, {@code false} otherwise. 128 */ hasMigratedToNewBlocking(Context context)129 public static boolean hasMigratedToNewBlocking(Context context) { 130 return StrictModeUtils.bypass( 131 () -> 132 PreferenceManager.getDefaultSharedPreferences(context) 133 .getBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, false)); 134 } 135 136 /** 137 * Called to inform this class whether the user has fully migrated to use {@link 138 * android.provider.BlockedNumberContract} blocking or not. 139 * 140 * @param hasMigrated {@code true} if the user has migrated, {@code false} otherwise. 141 */ setHasMigratedToNewBlocking(Context context, boolean hasMigrated)142 public static void setHasMigratedToNewBlocking(Context context, boolean hasMigrated) { 143 PreferenceManager.getDefaultSharedPreferences(context) 144 .edit() 145 .putBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, hasMigrated) 146 .apply(); 147 } 148 149 /** 150 * Gets the content {@link Uri} for number filtering. 151 * 152 * @param id The optional id to append with the base content uri. 153 * @return The Uri for number filtering. 154 */ getContentUri(Context context, @Nullable Integer id)155 public static Uri getContentUri(Context context, @Nullable Integer id) { 156 if (id == null) { 157 return getBaseUri(context); 158 } 159 return ContentUris.withAppendedId(getBaseUri(context), id); 160 } 161 getBaseUri(Context context)162 private static Uri getBaseUri(Context context) { 163 // Explicit version check to aid static analysis 164 return useNewFiltering(context) ? BlockedNumbers.CONTENT_URI : FilteredNumber.CONTENT_URI; 165 } 166 167 /** 168 * Removes any null column names from the given projection array. This method is intended to be 169 * used to strip out any column names that aren't available in every version of number blocking. 170 * Example: {@literal getContext().getContentResolver().query( someUri, // Filtering ensures that 171 * no non-existent columns are queried FilteredNumberCompat.filter(new String[] 172 * {FilteredNumberCompat.getIdColumnName(), FilteredNumberCompat.getTypeColumnName()}, 173 * FilteredNumberCompat.getE164NumberColumnName() + " = ?", new String[] {e164Number}); } 174 * 175 * @param projection The projection array. 176 * @return The filtered projection array. 177 */ 178 @Nullable filter(@ullable String[] projection)179 public static String[] filter(@Nullable String[] projection) { 180 if (projection == null) { 181 return null; 182 } 183 List<String> filtered = new ArrayList<>(); 184 for (String column : projection) { 185 if (column != null) { 186 filtered.add(column); 187 } 188 } 189 return filtered.toArray(new String[filtered.size()]); 190 } 191 192 /** 193 * Creates a new {@link ContentValues} suitable for inserting in the filtered number table. 194 * 195 * @param number The unformatted number to insert. 196 * @param e164Number (optional) The number to insert formatted to E164 standard. 197 * @param countryIso (optional) The country iso to use to format the number. 198 * @return The ContentValues to insert. 199 * @throws NullPointerException If number is null. 200 */ newBlockNumberContentValues( Context context, String number, @Nullable String e164Number, @Nullable String countryIso)201 public static ContentValues newBlockNumberContentValues( 202 Context context, String number, @Nullable String e164Number, @Nullable String countryIso) { 203 ContentValues contentValues = new ContentValues(); 204 contentValues.put(getOriginalNumberColumnName(context), Objects.requireNonNull(number)); 205 if (!useNewFiltering(context)) { 206 if (e164Number == null) { 207 e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); 208 } 209 contentValues.put(getE164NumberColumnName(context), e164Number); 210 contentValues.put(getCountryIsoColumnName(context), countryIso); 211 contentValues.put(getTypeColumnName(context), FilteredNumberTypes.BLOCKED_NUMBER); 212 contentValues.put(getSourceColumnName(context), FilteredNumberSources.USER); 213 } 214 return contentValues; 215 } 216 217 /** 218 * Shows block number migration dialog if necessary. 219 * 220 * @param fragmentManager The {@link FragmentManager} used to show fragments. 221 * @param listener The {@link BlockedNumbersMigrator.Listener} to call when migration is complete. 222 * @return boolean True if migration dialog is shown. 223 */ maybeShowBlockNumberMigrationDialog( Context context, FragmentManager fragmentManager, BlockedNumbersMigrator.Listener listener)224 public static boolean maybeShowBlockNumberMigrationDialog( 225 Context context, FragmentManager fragmentManager, BlockedNumbersMigrator.Listener listener) { 226 if (shouldShowMigrationDialog(context)) { 227 LogUtil.i( 228 "FilteredNumberCompat.maybeShowBlockNumberMigrationDialog", 229 "maybeShowBlockNumberMigrationDialog - showing migration dialog"); 230 MigrateBlockedNumbersDialogFragment.newInstance(new BlockedNumbersMigrator(context), listener) 231 .show(fragmentManager, "MigrateBlockedNumbers"); 232 return true; 233 } 234 return false; 235 } 236 shouldShowMigrationDialog(Context context)237 private static boolean shouldShowMigrationDialog(Context context) { 238 return canUseNewFiltering() && !hasMigratedToNewBlocking(context); 239 } 240 241 /** 242 * Creates the {@link Intent} which opens the blocked numbers management interface. 243 * 244 * @param context The {@link Context}. 245 * @return The intent. 246 */ createManageBlockedNumbersIntent(Context context)247 public static Intent createManageBlockedNumbersIntent(Context context) { 248 // Explicit version check to aid static analysis 249 if (canUseNewFiltering() && hasMigratedToNewBlocking(context)) { 250 return context.getSystemService(TelecomManager.class).createManageBlockedNumbersIntent(); 251 } 252 Intent intent = new Intent("com.android.dialer.action.BLOCKED_NUMBERS_SETTINGS"); 253 intent.setPackage(context.getPackageName()); 254 return intent; 255 } 256 257 /** 258 * Method used to determine if block operations are possible. 259 * 260 * @param context The {@link Context}. 261 * @return {@code true} if the app and user can block numbers, {@code false} otherwise. 262 */ canAttemptBlockOperations(Context context)263 public static boolean canAttemptBlockOperations(Context context) { 264 if (canAttemptBlockOperationsForTest != null) { 265 return canAttemptBlockOperationsForTest; 266 } 267 268 // Great Wall blocking, must be primary user and the default or system dialer 269 // TODO(maxwelb): check that we're the system Dialer 270 return TelecomUtil.isDefaultDialer(context) 271 && safeBlockedNumbersContractCanCurrentUserBlockNumbers(context); 272 } 273 274 @VisibleForTesting(otherwise = VisibleForTesting.NONE) setCanAttemptBlockOperationsForTest(boolean canAttempt)275 public static void setCanAttemptBlockOperationsForTest(boolean canAttempt) { 276 canAttemptBlockOperationsForTest = canAttempt; 277 } 278 279 /** 280 * Used to determine if the call blocking settings can be opened. 281 * 282 * @param context The {@link Context}. 283 * @return {@code true} if the current user can open the call blocking settings, {@code false} 284 * otherwise. 285 */ canCurrentUserOpenBlockSettings(Context context)286 public static boolean canCurrentUserOpenBlockSettings(Context context) { 287 // BlockedNumberContract blocking, verify through Contract API 288 return TelecomUtil.isDefaultDialer(context) 289 && safeBlockedNumbersContractCanCurrentUserBlockNumbers(context); 290 } 291 292 /** 293 * Calls {@link BlockedNumberContract#canCurrentUserBlockNumbers(Context)} in such a way that it 294 * never throws an exception. While on the CryptKeeper screen, the BlockedNumberContract isn't 295 * available, using this method ensures that the Dialer doesn't crash when on that screen. 296 * 297 * @param context The {@link Context}. 298 * @return the result of BlockedNumberContract#canCurrentUserBlockNumbers, or {@code false} if an 299 * exception was thrown. 300 */ safeBlockedNumbersContractCanCurrentUserBlockNumbers(Context context)301 private static boolean safeBlockedNumbersContractCanCurrentUserBlockNumbers(Context context) { 302 try { 303 return BlockedNumberContract.canCurrentUserBlockNumbers(context); 304 } catch (Exception e) { 305 LogUtil.e( 306 "FilteredNumberCompat.safeBlockedNumbersContractCanCurrentUserBlockNumbers", 307 "Exception while querying BlockedNumberContract", 308 e); 309 return false; 310 } 311 } 312 } 313