diff options
Diffstat (limited to 'java/com/android/dialer/preferredsim/PreferredAccountWorker.java')
-rw-r--r-- | java/com/android/dialer/preferredsim/PreferredAccountWorker.java | 273 |
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; + } +} |