summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/blocking/FilteredNumberCompat.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/blocking/FilteredNumberCompat.java')
-rw-r--r--java/com/android/dialer/blocking/FilteredNumberCompat.java320
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;
+ }
+ }
+}