1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
|
/*
* 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.compat;
import com.google.common.base.Preconditions;
import android.app.FragmentManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.UserManager;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.compat.TelecomManagerUtil;
import com.android.contacts.common.testing.NeededForTesting;
import com.android.dialer.DialerApplication;
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.filterednumber.BlockNumberDialogFragment;
import com.android.dialer.filterednumber.BlockNumberDialogFragment.Callback;
import com.android.dialer.filterednumber.BlockedNumbersMigrator;
import com.android.dialer.filterednumber.BlockedNumbersSettingsActivity;
import com.android.dialer.filterednumber.MigrateBlockedNumbersDialogFragment;
import com.android.dialerbind.ObjectFactory;
import com.android.incallui.Log;
import java.util.ArrayList;
import java.util.List;
/**
* 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 final String TAG = "FilteredNumberCompat";
protected static final String HAS_MIGRATED_TO_NEW_BLOCKING_KEY = "migratedToNewBlocking";
private static Boolean isEnabledForTest;
/**
* @return The column name for ID in the filtered number database.
*/
public static String getIdColumnName() {
return useNewFiltering() ? BlockedNumbersSdkCompat._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() {
return useNewFiltering() ? 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() {
return useNewFiltering() ? null : FilteredNumberColumns.SOURCE;
}
/**
* @return The column name for the original number in the filtered number database.
*/
public static String getOriginalNumberColumnName() {
return useNewFiltering() ? BlockedNumbersSdkCompat.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() {
return useNewFiltering() ? null : FilteredNumberColumns.COUNTRY_ISO;
}
/**
* @return The column name for the e164 formatted number in the filtered number database.
*/
public static String getE164NumberColumnName() {
return useNewFiltering() ? BlockedNumbersSdkCompat.E164_NUMBER
: FilteredNumberColumns.NORMALIZED_NUMBER;
}
/**
* @return {@code true} if the current SDK version supports using new filtering, {@code false}
* otherwise.
*/
public static boolean canUseNewFiltering() {
if (isEnabledForTest != null) {
return CompatUtils.isNCompatible() && isEnabledForTest;
}
return CompatUtils.isNCompatible() && ObjectFactory
.isNewBlockingEnabled(DialerApplication.getContext());
}
/**
* @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() {
return canUseNewFiltering() && hasMigratedToNewBlocking();
}
/**
* @return {@code true} if the user has migrated to use
* {@link android.provider.BlockedNumberContract} blocking, {@code false} otherwise.
*/
public static boolean hasMigratedToNewBlocking() {
return PreferenceManager.getDefaultSharedPreferences(DialerApplication.getContext())
.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.
*/
@NeededForTesting
public static void setHasMigratedToNewBlocking(boolean hasMigrated) {
PreferenceManager.getDefaultSharedPreferences(DialerApplication.getContext()).edit()
.putBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, hasMigrated).apply();
}
@NeededForTesting
public static void setIsEnabledForTest(Boolean isEnabled) {
isEnabledForTest = isEnabled;
}
/**
* 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(@Nullable Integer id) {
if (id == null) {
return getBaseUri();
}
return ContentUris.withAppendedId(getBaseUri(), id);
}
private static Uri getBaseUri() {
return useNewFiltering() ? BlockedNumbersSdkCompat.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(String number,
@Nullable String e164Number, @Nullable String countryIso) {
ContentValues contentValues = new ContentValues();
contentValues.put(getOriginalNumberColumnName(), Preconditions.checkNotNull(number));
if (!useNewFiltering()) {
if (e164Number == null) {
e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
}
contentValues.put(getE164NumberColumnName(), e164Number);
contentValues.put(getCountryIsoColumnName(), countryIso);
contentValues.put(getTypeColumnName(), FilteredNumberTypes.BLOCKED_NUMBER);
contentValues.put(getSourceColumnName(), FilteredNumberSources.USER);
}
return contentValues;
}
/**
* Shows the flow of {@link android.app.DialogFragment}s for blocking or unblocking numbers.
*
* @param blockId The id into the blocked numbers database.
* @param number The number to block or unblock.
* @param countryIso The countryIso used to format the given number.
* @param displayNumber The form of the number to block, suitable for displaying.
* @param parentViewId The id for the containing view of the Dialog.
* @param fragmentManager The {@link FragmentManager} used to show fragments.
* @param callback (optional) The {@link Callback} to call when the block or unblock operation
* is complete.
*/
public static void showBlockNumberDialogFlow(final ContentResolver contentResolver,
final Integer blockId, final String number, final String countryIso,
final String displayNumber, final Integer parentViewId,
final FragmentManager fragmentManager, @Nullable final Callback callback) {
Log.i(TAG, "showBlockNumberDialogFlow - start");
// If the user is blocking a number and isn't using the framework solution when they
// should be, show the migration dialog
if (shouldShowMigrationDialog(blockId == null)) {
Log.i(TAG, "showBlockNumberDialogFlow - showing migration dialog");
MigrateBlockedNumbersDialogFragment
.newInstance(new BlockedNumbersMigrator(contentResolver),
new BlockedNumbersMigrator.Listener() {
@Override
public void onComplete() {
Log.i(TAG, "showBlockNumberDialogFlow - listener showing block "
+ "number dialog");
BlockNumberDialogFragment
.show(null, number, countryIso, displayNumber,
parentViewId,
fragmentManager, callback);
}
}).show(fragmentManager, "MigrateBlockedNumbers");
return;
}
Log.i(TAG, "showBlockNumberDialogFlow - showing block number dialog");
BlockNumberDialogFragment
.show(blockId, number, countryIso, displayNumber, parentViewId, fragmentManager,
callback);
}
private static boolean shouldShowMigrationDialog(boolean isBlocking) {
return isBlocking && canUseNewFiltering() && !hasMigratedToNewBlocking();
}
/**
* 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) {
if (canUseNewFiltering() && hasMigratedToNewBlocking()) {
return TelecomManagerUtil.createManageBlockedNumbersIntent(
(TelecomManager) context.getSystemService(Context.TELECOM_SERVICE));
}
return new Intent(context, BlockedNumbersSettingsActivity.class);
}
/**
* 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 (!CompatUtils.isNCompatible()) {
// Dialer blocking, must be primary user
return UserManagerCompat.isSystemUser(
(UserManager) context.getSystemService(Context.USER_SERVICE));
}
// Great Wall blocking, must be primary user and the default or system dialer
// TODO(maxwelb): check that we're the default or system Dialer
return BlockedNumbersSdkCompat.canCurrentUserBlockNumbers(context);
}
/**
* 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 (!CompatUtils.isNCompatible()) {
// Dialer blocking, must be primary user
return UserManagerCompat.isSystemUser(
(UserManager) context.getSystemService(Context.USER_SERVICE));
}
// BlockedNumberContract blocking, verify through Contract API
return BlockedNumbersSdkCompat.canCurrentUserBlockNumbers(context);
}
}
|