summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/preferredsim/PreferredAccountWorker.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/preferredsim/PreferredAccountWorker.java')
-rw-r--r--java/com/android/dialer/preferredsim/PreferredAccountWorker.java273
1 files changed, 273 insertions, 0 deletions
diff --git a/java/com/android/dialer/preferredsim/PreferredAccountWorker.java b/java/com/android/dialer/preferredsim/PreferredAccountWorker.java
new file mode 100644
index 000000000..bfaaa7cde
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/PreferredAccountWorker.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2018 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.preferredsim;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccountHandle;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.configprovider.ConfigProviderBindings;
+import com.android.dialer.preferredsim.PreferredAccountWorker.Result;
+import com.android.dialer.preferredsim.PreferredSimFallbackContract.PreferredSim;
+import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
+import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion;
+import com.android.dialer.util.PermissionsUtil;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+
+/** Query a preferred SIM to make a call with. */
+public class PreferredAccountWorker implements Worker<Context, Result> {
+
+ /** The result of the worker. */
+ @AutoValue
+ public abstract static class Result {
+
+ /** The preferred phone account for the number. Absent if not set or invalid. */
+ public abstract Optional<PhoneAccountHandle> getPhoneAccountHandle();
+
+ /**
+ * {@link android.provider.ContactsContract.Data#_ID} of the row matching the number. If the
+ * preferred account is to be set it should be stored in this row
+ */
+ public abstract Optional<String> getDataId();
+
+ public abstract Optional<Suggestion> getSuggestion();
+
+ static Builder builder() {
+ return new AutoValue_PreferredAccountWorker_Result.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+
+ public abstract Builder setPhoneAccountHandle(
+ Optional<PhoneAccountHandle> phoneAccountHandle);
+
+ public abstract Builder setDataId(Optional<String> dataId);
+
+ public abstract Builder setSuggestion(Optional<Suggestion> suggestion);
+
+ public abstract Result build();
+ }
+ }
+
+ @VisibleForTesting
+ public static final String METADATA_SUPPORTS_PREFERRED_SIM =
+ "supports_per_number_preferred_account";
+
+ private final String phoneNumber;
+
+ public PreferredAccountWorker(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ @NonNull
+ @Override
+ @WorkerThread
+ public Result doInBackground(Context context) throws Throwable {
+ Result.Builder resultBuilder = Result.builder();
+ if (!isPreferredSimEnabled(context)) {
+ return resultBuilder.build();
+ }
+ if (!PermissionsUtil.hasContactsReadPermissions(context)) {
+ LogUtil.i("PreferredAccountWorker.doInBackground", "missing READ_CONTACTS permission");
+ return resultBuilder.build();
+ }
+ Optional<String> dataId = getDataId(context, phoneNumber);
+ Optional<PhoneAccountHandle> phoneAccountHandle = Optional.absent();
+ if (dataId.isPresent()) {
+ resultBuilder.setDataId(dataId);
+ phoneAccountHandle = getPreferredAccount(context, dataId.get());
+ }
+ resultBuilder.setPhoneAccountHandle(phoneAccountHandle);
+ Optional<Suggestion> suggestion = Optional.absent();
+ if (!phoneAccountHandle.isPresent()) {
+ suggestion =
+ SimSuggestionComponent.get(context)
+ .getSuggestionProvider()
+ .getSuggestion(context, phoneNumber);
+ resultBuilder.setSuggestion(suggestion);
+ }
+ return resultBuilder.build();
+ }
+
+ @WorkerThread
+ @NonNull
+ private static Optional<String> getDataId(
+ @NonNull Context context, @Nullable String phoneNumber) {
+ Assert.isWorkerThread();
+ if (VERSION.SDK_INT < VERSION_CODES.N) {
+ return Optional.absent();
+ }
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)),
+ new String[] {PhoneLookup.DATA_ID},
+ null,
+ null,
+ null)) {
+ if (cursor == null) {
+ return Optional.absent();
+ }
+ ImmutableSet<String> validAccountTypes = PreferredAccountUtil.getValidAccountTypes(context);
+ String result = null;
+ while (cursor.moveToNext()) {
+ Optional<String> accountType =
+ getAccountType(context.getContentResolver(), cursor.getLong(0));
+ if (accountType.isPresent() && !validAccountTypes.contains(accountType.get())) {
+ // Empty accountType is treated as writable
+ LogUtil.i("CallingAccountSelector.getDataId", "ignoring non-writable " + accountType);
+ continue;
+ }
+ if (result != null && !result.equals(cursor.getString(0))) {
+ // TODO(twyen): if there are multiple entries attempt to grab from the contact that
+ // initiated the call.
+ LogUtil.i("CallingAccountSelector.getDataId", "lookup result not unique, ignoring");
+ return Optional.absent();
+ }
+ result = cursor.getString(0);
+ }
+ return Optional.fromNullable(result);
+ }
+ }
+
+ @WorkerThread
+ private static Optional<String> getAccountType(ContentResolver contentResolver, long dataId) {
+ Assert.isWorkerThread();
+ Optional<Long> rawContactId = getRawContactId(contentResolver, dataId);
+ if (!rawContactId.isPresent()) {
+ return Optional.absent();
+ }
+ try (Cursor cursor =
+ contentResolver.query(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId.get()),
+ new String[] {RawContacts.ACCOUNT_TYPE},
+ null,
+ null,
+ null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return Optional.fromNullable(cursor.getString(0));
+ }
+ }
+
+ @WorkerThread
+ private static Optional<Long> getRawContactId(ContentResolver contentResolver, long dataId) {
+ Assert.isWorkerThread();
+ try (Cursor cursor =
+ contentResolver.query(
+ ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
+ new String[] {Data.RAW_CONTACT_ID},
+ null,
+ null,
+ null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return Optional.of(cursor.getLong(0));
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private static Optional<PhoneAccountHandle> getPreferredAccount(
+ @NonNull Context context, @NonNull String dataId) {
+ Assert.isWorkerThread();
+ Assert.isNotNull(dataId);
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(
+ PreferredSimFallbackContract.CONTENT_URI,
+ new String[] {
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_ID
+ },
+ PreferredSim.DATA_ID + " = ?",
+ new String[] {dataId},
+ null)) {
+ if (cursor == null) {
+ return Optional.absent();
+ }
+ if (!cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return PreferredAccountUtil.getValidPhoneAccount(
+ context, cursor.getString(0), cursor.getString(1));
+ }
+ }
+
+ @WorkerThread
+ private static boolean isPreferredSimEnabled(Context context) {
+ Assert.isWorkerThread();
+ if (!ConfigProviderBindings.get(context).getBoolean("preferred_sim_enabled", true)) {
+ return false;
+ }
+
+ Intent quickContactIntent = getQuickContactIntent();
+ ResolveInfo resolveInfo =
+ context
+ .getPackageManager()
+ .resolveActivity(quickContactIntent, PackageManager.GET_META_DATA);
+ if (resolveInfo == null
+ || resolveInfo.activityInfo == null
+ || resolveInfo.activityInfo.applicationInfo == null
+ || resolveInfo.activityInfo.applicationInfo.metaData == null) {
+ LogUtil.e("CallingAccountSelector.isPreferredSimEnabled", "cannot resolve quick contact app");
+ return false;
+ }
+ if (!resolveInfo.activityInfo.applicationInfo.metaData.getBoolean(
+ METADATA_SUPPORTS_PREFERRED_SIM, false)) {
+ LogUtil.i(
+ "CallingAccountSelector.isPreferredSimEnabled",
+ "system contacts does not support preferred SIM");
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ public static Intent getQuickContactIntent() {
+ Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setData(Contacts.CONTENT_URI.buildUpon().appendPath("1").build());
+ return intent;
+ }
+}