diff options
Diffstat (limited to 'java/com/android/dialer/blocking/FilteredNumberCompat.java')
-rw-r--r-- | java/com/android/dialer/blocking/FilteredNumberCompat.java | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/java/com/android/dialer/blocking/FilteredNumberCompat.java b/java/com/android/dialer/blocking/FilteredNumberCompat.java new file mode 100644 index 000000000..0ee85d897 --- /dev/null +++ b/java/com/android/dialer/blocking/FilteredNumberCompat.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.blocking; + +import android.annotation.TargetApi; +import android.app.FragmentManager; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.UserManager; +import android.preference.PreferenceManager; +import android.provider.BlockedNumberContract; +import android.provider.BlockedNumberContract.BlockedNumbers; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.telecom.TelecomManager; +import android.telephony.PhoneNumberUtils; +import com.android.dialer.common.LogUtil; +import com.android.dialer.database.FilteredNumberContract.FilteredNumber; +import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; +import com.android.dialer.database.FilteredNumberContract.FilteredNumberSources; +import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; +import com.android.dialer.telecom.TelecomUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Compatibility class to encapsulate logic to switch between call blocking using {@link + * com.android.dialer.database.FilteredNumberContract} and using {@link + * android.provider.BlockedNumberContract}. This class should be used rather than explicitly + * referencing columns from either contract class in situations where both blocking solutions may be + * used. + */ +public class FilteredNumberCompat { + + private static Boolean canAttemptBlockOperationsForTest; + + @VisibleForTesting + public static final String HAS_MIGRATED_TO_NEW_BLOCKING_KEY = "migratedToNewBlocking"; + + /** @return The column name for ID in the filtered number database. */ + public static String getIdColumnName(Context context) { + return useNewFiltering(context) ? BlockedNumbers.COLUMN_ID : FilteredNumberColumns._ID; + } + + /** + * @return The column name for type in the filtered number database. Will be {@code null} for the + * framework blocking implementation. + */ + @Nullable + public static String getTypeColumnName(Context context) { + return useNewFiltering(context) ? null : FilteredNumberColumns.TYPE; + } + + /** + * @return The column name for source in the filtered number database. Will be {@code null} for + * the framework blocking implementation + */ + @Nullable + public static String getSourceColumnName(Context context) { + return useNewFiltering(context) ? null : FilteredNumberColumns.SOURCE; + } + + /** @return The column name for the original number in the filtered number database. */ + public static String getOriginalNumberColumnName(Context context) { + return useNewFiltering(context) + ? BlockedNumbers.COLUMN_ORIGINAL_NUMBER + : FilteredNumberColumns.NUMBER; + } + + /** + * @return The column name for country iso in the filtered number database. Will be {@code null} + * the framework blocking implementation + */ + @Nullable + public static String getCountryIsoColumnName(Context context) { + return useNewFiltering(context) ? null : FilteredNumberColumns.COUNTRY_ISO; + } + + /** @return The column name for the e164 formatted number in the filtered number database. */ + public static String getE164NumberColumnName(Context context) { + return useNewFiltering(context) + ? BlockedNumbers.COLUMN_E164_NUMBER + : FilteredNumberColumns.NORMALIZED_NUMBER; + } + + /** + * @return {@code true} if the current SDK version supports using new filtering, {@code false} + * otherwise. + */ + public static boolean canUseNewFiltering() { + return VERSION.SDK_INT >= VERSION_CODES.N; + } + + /** + * @return {@code true} if the new filtering should be used, i.e. it's enabled and any necessary + * migration has been performed, {@code false} otherwise. + */ + public static boolean useNewFiltering(Context context) { + return canUseNewFiltering() && hasMigratedToNewBlocking(context); + } + + /** + * @return {@code true} if the user has migrated to use {@link + * android.provider.BlockedNumberContract} blocking, {@code false} otherwise. + */ + public static boolean hasMigratedToNewBlocking(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, false); + } + + /** + * Called to inform this class whether the user has fully migrated to use {@link + * android.provider.BlockedNumberContract} blocking or not. + * + * @param hasMigrated {@code true} if the user has migrated, {@code false} otherwise. + */ + public static void setHasMigratedToNewBlocking(Context context, boolean hasMigrated) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, hasMigrated) + .apply(); + } + + /** + * Gets the content {@link Uri} for number filtering. + * + * @param id The optional id to append with the base content uri. + * @return The Uri for number filtering. + */ + public static Uri getContentUri(Context context, @Nullable Integer id) { + if (id == null) { + return getBaseUri(context); + } + return ContentUris.withAppendedId(getBaseUri(context), id); + } + + private static Uri getBaseUri(Context context) { + // Explicit version check to aid static analysis + return useNewFiltering(context) && VERSION.SDK_INT >= VERSION_CODES.N + ? BlockedNumbers.CONTENT_URI + : FilteredNumber.CONTENT_URI; + } + + /** + * Removes any null column names from the given projection array. This method is intended to be + * used to strip out any column names that aren't available in every version of number blocking. + * Example: {@literal getContext().getContentResolver().query( someUri, // Filtering ensures that + * no non-existant columns are queried FilteredNumberCompat.filter(new String[] + * {FilteredNumberCompat.getIdColumnName(), FilteredNumberCompat.getTypeColumnName()}, + * FilteredNumberCompat.getE164NumberColumnName() + " = ?", new String[] {e164Number}); } + * + * @param projection The projection array. + * @return The filtered projection array. + */ + @Nullable + public static String[] filter(@Nullable String[] projection) { + if (projection == null) { + return null; + } + List<String> filtered = new ArrayList<>(); + for (String column : projection) { + if (column != null) { + filtered.add(column); + } + } + return filtered.toArray(new String[filtered.size()]); + } + + /** + * Creates a new {@link ContentValues} suitable for inserting in the filtered number table. + * + * @param number The unformatted number to insert. + * @param e164Number (optional) The number to insert formatted to E164 standard. + * @param countryIso (optional) The country iso to use to format the number. + * @return The ContentValues to insert. + * @throws NullPointerException If number is null. + */ + public static ContentValues newBlockNumberContentValues( + Context context, String number, @Nullable String e164Number, @Nullable String countryIso) { + ContentValues contentValues = new ContentValues(); + contentValues.put(getOriginalNumberColumnName(context), Objects.requireNonNull(number)); + if (!useNewFiltering(context)) { + if (e164Number == null) { + e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); + } + contentValues.put(getE164NumberColumnName(context), e164Number); + contentValues.put(getCountryIsoColumnName(context), countryIso); + contentValues.put(getTypeColumnName(context), FilteredNumberTypes.BLOCKED_NUMBER); + contentValues.put(getSourceColumnName(context), FilteredNumberSources.USER); + } + return contentValues; + } + + /** + * Shows block number migration dialog if necessary. + * + * @param fragmentManager The {@link FragmentManager} used to show fragments. + * @param listener The {@link BlockedNumbersMigrator.Listener} to call when migration is complete. + * @return boolean True if migration dialog is shown. + */ + public static boolean maybeShowBlockNumberMigrationDialog( + Context context, FragmentManager fragmentManager, BlockedNumbersMigrator.Listener listener) { + if (shouldShowMigrationDialog(context)) { + LogUtil.i( + "FilteredNumberCompat.maybeShowBlockNumberMigrationDialog", + "maybeShowBlockNumberMigrationDialog - showing migration dialog"); + MigrateBlockedNumbersDialogFragment.newInstance(new BlockedNumbersMigrator(context), listener) + .show(fragmentManager, "MigrateBlockedNumbers"); + return true; + } + return false; + } + + private static boolean shouldShowMigrationDialog(Context context) { + return canUseNewFiltering() && !hasMigratedToNewBlocking(context); + } + + /** + * Creates the {@link Intent} which opens the blocked numbers management interface. + * + * @param context The {@link Context}. + * @return The intent. + */ + public static Intent createManageBlockedNumbersIntent(Context context) { + // Explicit version check to aid static analysis + if (canUseNewFiltering() + && hasMigratedToNewBlocking(context) + && VERSION.SDK_INT >= VERSION_CODES.N) { + return context.getSystemService(TelecomManager.class).createManageBlockedNumbersIntent(); + } + Intent intent = new Intent("com.android.dialer.action.BLOCKED_NUMBERS_SETTINGS"); + intent.setPackage(context.getPackageName()); + return intent; + } + + /** + * Method used to determine if block operations are possible. + * + * @param context The {@link Context}. + * @return {@code true} if the app and user can block numbers, {@code false} otherwise. + */ + public static boolean canAttemptBlockOperations(Context context) { + if (canAttemptBlockOperationsForTest != null) { + return canAttemptBlockOperationsForTest; + } + + if (VERSION.SDK_INT < VERSION_CODES.N) { + // Dialer blocking, must be primary user + return context.getSystemService(UserManager.class).isSystemUser(); + } + + // Great Wall blocking, must be primary user and the default or system dialer + // TODO: check that we're the system Dialer + return TelecomUtil.isDefaultDialer(context) + && safeBlockedNumbersContractCanCurrentUserBlockNumbers(context); + } + + static void setCanAttemptBlockOperationsForTest(boolean canAttempt) { + canAttemptBlockOperationsForTest = canAttempt; + } + + /** + * Used to determine if the call blocking settings can be opened. + * + * @param context The {@link Context}. + * @return {@code true} if the current user can open the call blocking settings, {@code false} + * otherwise. + */ + public static boolean canCurrentUserOpenBlockSettings(Context context) { + if (VERSION.SDK_INT < VERSION_CODES.N) { + // Dialer blocking, must be primary user + return context.getSystemService(UserManager.class).isSystemUser(); + } + // BlockedNumberContract blocking, verify through Contract API + return TelecomUtil.isDefaultDialer(context) + && safeBlockedNumbersContractCanCurrentUserBlockNumbers(context); + } + + /** + * Calls {@link BlockedNumberContract#canCurrentUserBlockNumbers(Context)} in such a way that it + * never throws an exception. While on the CryptKeeper screen, the BlockedNumberContract isn't + * available, using this method ensures that the Dialer doesn't crash when on that screen. + * + * @param context The {@link Context}. + * @return the result of BlockedNumberContract#canCurrentUserBlockNumbers, or {@code false} if an + * exception was thrown. + */ + @TargetApi(VERSION_CODES.N) + private static boolean safeBlockedNumbersContractCanCurrentUserBlockNumbers(Context context) { + try { + return BlockedNumberContract.canCurrentUserBlockNumbers(context); + } catch (Exception e) { + LogUtil.e( + "FilteredNumberCompat.safeBlockedNumbersContractCanCurrentUserBlockNumbers", + "Exception while querying BlockedNumberContract", + e); + return false; + } + } +} |