| /* |
| * 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.settings.wifi.calling; |
| |
| import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; |
| |
| import android.app.PendingIntent; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.os.PersistableBundle; |
| import android.provider.Settings; |
| import androidx.core.graphics.drawable.IconCompat; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import androidx.slice.Slice; |
| import androidx.slice.builders.ListBuilder; |
| import androidx.slice.builders.SliceAction; |
| |
| import com.android.ims.ImsManager; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.settings.R; |
| import com.android.settings.slices.SettingsSliceProvider; |
| import com.android.settings.slices.SliceBroadcastReceiver; |
| import com.android.settings.slices.SliceBuilderUtils; |
| |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| |
| /** |
| * Helper class to control slices for wifi calling settings. |
| */ |
| public class WifiCallingSliceHelper { |
| |
| private static final String TAG = "WifiCallingSliceHelper"; |
| |
| /** |
| * Settings slice path to wifi calling setting. |
| */ |
| public static final String PATH_WIFI_CALLING = "wifi_calling"; |
| |
| /** |
| * Action passed for changes to wifi calling slice (toggle). |
| */ |
| public static final String ACTION_WIFI_CALLING_CHANGED = |
| "com.android.settings.wifi.calling.action.WIFI_CALLING_CHANGED"; |
| |
| /** |
| * Action for Wifi calling Settings activity which |
| * allows setting configuration for Wifi calling |
| * related settings |
| */ |
| public static final String ACTION_WIFI_CALLING_SETTINGS_ACTIVITY = |
| "android.settings.WIFI_CALLING_SETTINGS"; |
| |
| /** |
| * Full {@link Uri} for the Wifi Calling Slice. |
| */ |
| public static final Uri WIFI_CALLING_URI = new Uri.Builder() |
| .scheme(ContentResolver.SCHEME_CONTENT) |
| .authority(SettingsSliceProvider.SLICE_AUTHORITY) |
| .appendPath(PATH_WIFI_CALLING) |
| .build(); |
| |
| /** |
| * Timeout for querying wifi calling setting from ims manager. |
| */ |
| private static final int TIMEOUT_MILLIS = 2000; |
| |
| protected SubscriptionManager mSubscriptionManager; |
| private final Context mContext; |
| |
| @VisibleForTesting |
| public WifiCallingSliceHelper(Context context) { |
| mContext = context; |
| } |
| |
| /** |
| * Returns Slice object for wifi calling settings. |
| * |
| * If wifi calling is being turned on and if wifi calling activation is needed for the current |
| * carrier, this method will return Slice with instructions to go to Settings App. |
| * |
| * If wifi calling is not supported for the current carrier, this method will return slice with |
| * not supported message. |
| * |
| * If wifi calling setting can be changed, this method will return the slice to toggle wifi |
| * calling option with ACTION_WIFI_CALLING_CHANGED as endItem. |
| */ |
| public Slice createWifiCallingSlice(Uri sliceUri) { |
| final int subId = getDefaultVoiceSubId(); |
| final String carrierName = getSimCarrierName(); |
| |
| if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| Log.d(TAG, "Invalid subscription Id"); |
| return getNonActionableWifiCallingSlice( |
| mContext.getString(R.string.wifi_calling_settings_title), |
| mContext.getString(R.string.wifi_calling_not_supported, carrierName), |
| sliceUri, getSettingsIntent(mContext)); |
| } |
| |
| final ImsManager imsManager = getImsManager(subId); |
| |
| if (!imsManager.isWfcEnabledByPlatform() |
| || !imsManager.isWfcProvisionedOnDevice()) { |
| Log.d(TAG, "Wifi calling is either not provisioned or not enabled by Platform"); |
| return getNonActionableWifiCallingSlice( |
| mContext.getString(R.string.wifi_calling_settings_title), |
| mContext.getString(R.string.wifi_calling_not_supported, carrierName), |
| sliceUri, getSettingsIntent(mContext)); |
| } |
| |
| try { |
| final boolean isWifiCallingEnabled = isWifiCallingEnabled(imsManager); |
| final Intent activationAppIntent = |
| getWifiCallingCarrierActivityIntent(subId); |
| |
| // Send this actionable wifi calling slice to toggle the setting |
| // only when there is no need for wifi calling activation with the server |
| if (activationAppIntent != null && !isWifiCallingEnabled) { |
| Log.d(TAG, "Needs Activation"); |
| // Activation needed for the next action of the user |
| // Give instructions to go to settings app |
| return getNonActionableWifiCallingSlice( |
| mContext.getString(R.string.wifi_calling_settings_title), |
| mContext.getString( |
| R.string.wifi_calling_settings_activation_instructions), |
| sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); |
| } |
| return getWifiCallingSlice(sliceUri, mContext, isWifiCallingEnabled); |
| } catch (InterruptedException | TimeoutException | ExecutionException e) { |
| Log.e(TAG, "Unable to read the current WiFi calling status", e); |
| return getNonActionableWifiCallingSlice( |
| mContext.getString(R.string.wifi_calling_settings_title), |
| mContext.getString(R.string.wifi_calling_turn_on), |
| sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY)); |
| } |
| } |
| |
| private boolean isWifiCallingEnabled(ImsManager imsManager) |
| throws InterruptedException, ExecutionException, TimeoutException { |
| final FutureTask<Boolean> isWifiOnTask = new FutureTask<>(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return imsManager.isWfcEnabledByUser(); |
| } |
| }); |
| final ExecutorService executor = Executors.newSingleThreadExecutor(); |
| executor.execute(isWifiOnTask); |
| |
| Boolean isWifiEnabledByUser = false; |
| isWifiEnabledByUser = isWifiOnTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); |
| |
| return isWifiEnabledByUser && imsManager.isNonTtyOrTtyOnVolteEnabled(); |
| } |
| |
| /** |
| * Builds a toggle slice where the intent takes you to the wifi calling page and the toggle |
| * enables/disables wifi calling. |
| */ |
| private Slice getWifiCallingSlice(Uri sliceUri, Context mContext, |
| boolean isWifiCallingEnabled) { |
| |
| final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); |
| final String title = mContext.getString(R.string.wifi_calling_settings_title); |
| return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) |
| .setColor(R.color.material_blue_500) |
| .addRow(b -> b |
| .setTitle(title) |
| .addEndItem( |
| new SliceAction( |
| getBroadcastIntent(ACTION_WIFI_CALLING_CHANGED), |
| null /* actionTitle */, isWifiCallingEnabled)) |
| .setPrimaryAction(new SliceAction( |
| getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY), |
| icon, |
| title))) |
| .build(); |
| } |
| |
| protected ImsManager getImsManager(int subId) { |
| return ImsManager.getInstance(mContext, SubscriptionManager.getPhoneId(subId)); |
| } |
| |
| private Integer getWfcMode(ImsManager imsManager) |
| throws InterruptedException, ExecutionException, TimeoutException { |
| FutureTask<Integer> wfcModeTask = new FutureTask<>(new Callable<Integer>() { |
| @Override |
| public Integer call() { |
| return imsManager.getWfcMode(false); |
| } |
| }); |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| executor.execute(wfcModeTask); |
| return wfcModeTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Handles wifi calling setting change from wifi calling slice and posts notification. Should be |
| * called when intent action is ACTION_WIFI_CALLING_CHANGED. Executed in @WorkerThread |
| * |
| * @param intent action performed |
| */ |
| public void handleWifiCallingChanged(Intent intent) { |
| final int subId = getDefaultVoiceSubId(); |
| |
| if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| final ImsManager imsManager = getImsManager(subId); |
| if (imsManager.isWfcEnabledByPlatform() |
| || imsManager.isWfcProvisionedOnDevice()) { |
| final boolean currentValue = imsManager.isWfcEnabledByUser() |
| && imsManager.isNonTtyOrTtyOnVolteEnabled(); |
| final boolean newValue = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, |
| currentValue); |
| final Intent activationAppIntent = |
| getWifiCallingCarrierActivityIntent(subId); |
| if (!newValue || activationAppIntent == null) { |
| // If either the action is to turn off wifi calling setting |
| // or there is no activation involved - Update the setting |
| if (newValue != currentValue) { |
| imsManager.setWfcSetting(newValue); |
| } |
| } |
| } |
| } |
| // notify change in slice in any case to get re-queried. This would result in displaying |
| // appropriate message with the updated setting. |
| final Uri uri = SliceBuilderUtils.getUri(PATH_WIFI_CALLING, false /*isPlatformSlice*/); |
| mContext.getContentResolver().notifyChange(uri, null); |
| } |
| |
| /** |
| * Returns Slice with the title and subtitle provided as arguments with wifi signal Icon. |
| * |
| * @param title Title of the slice |
| * @param subtitle Subtitle of the slice |
| * @param sliceUri slice uri |
| * @return Slice with title and subtitle |
| */ |
| // TODO(b/79548264) asses different scenarios and return null instead of non-actionable slice |
| private Slice getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri, |
| PendingIntent primaryActionIntent) { |
| final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); |
| return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) |
| .setColor(R.color.material_blue_500) |
| .addRow(b -> b |
| .setTitle(title) |
| .setSubtitle(subtitle) |
| .setPrimaryAction(new SliceAction( |
| primaryActionIntent, icon, |
| title))) |
| .build(); |
| } |
| |
| /** |
| * Returns {@code true} when the key is enabled for the carrier, and {@code false} otherwise. |
| */ |
| private boolean isCarrierConfigManagerKeyEnabled(Context mContext, String key, |
| int subId, boolean defaultValue) { |
| final CarrierConfigManager configManager = getCarrierConfigManager(mContext); |
| boolean ret = false; |
| if (configManager != null) { |
| final PersistableBundle bundle = configManager.getConfigForSubId(subId); |
| if (bundle != null) { |
| ret = bundle.getBoolean(key, defaultValue); |
| } |
| } |
| return ret; |
| } |
| |
| protected CarrierConfigManager getCarrierConfigManager(Context mContext) { |
| return mContext.getSystemService(CarrierConfigManager.class); |
| } |
| |
| /** |
| * Returns the current default voice subId obtained from SubscriptionManager |
| */ |
| protected int getDefaultVoiceSubId() { |
| if (mSubscriptionManager == null) { |
| mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); |
| } |
| return SubscriptionManager.getDefaultVoiceSubscriptionId(); |
| } |
| |
| /** |
| * Returns Intent of the activation app required to activate wifi calling or null if there is no |
| * need for activation. |
| */ |
| protected Intent getWifiCallingCarrierActivityIntent(int subId) { |
| final CarrierConfigManager configManager = getCarrierConfigManager(mContext); |
| if (configManager == null) { |
| return null; |
| } |
| |
| final PersistableBundle bundle = configManager.getConfigForSubId(subId); |
| if (bundle == null) { |
| return null; |
| } |
| |
| final String carrierApp = bundle.getString( |
| CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING); |
| if (TextUtils.isEmpty(carrierApp)) { |
| return null; |
| } |
| |
| final ComponentName componentName = ComponentName.unflattenFromString(carrierApp); |
| if (componentName == null) { |
| return null; |
| } |
| |
| final Intent intent = new Intent(); |
| intent.setComponent(componentName); |
| return intent; |
| } |
| |
| /** |
| * @return {@link PendingIntent} to the Settings home page. |
| */ |
| public static PendingIntent getSettingsIntent(Context context) { |
| final Intent intent = new Intent(Settings.ACTION_SETTINGS); |
| return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */); |
| } |
| |
| private PendingIntent getBroadcastIntent(String action) { |
| final Intent intent = new Intent(action); |
| intent.setClass(mContext, SliceBroadcastReceiver.class); |
| return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, |
| PendingIntent.FLAG_CANCEL_CURRENT); |
| } |
| |
| /** |
| * Returns PendingIntent to start activity specified by action |
| */ |
| private PendingIntent getActivityIntent(String action) { |
| final Intent intent = new Intent(action); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); |
| } |
| |
| /** |
| * Returns carrier id name of the current Subscription |
| */ |
| private String getSimCarrierName() { |
| final TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); |
| final CharSequence carrierName = telephonyManager.getSimCarrierIdName(); |
| if (carrierName == null) { |
| return mContext.getString(R.string.carrier); |
| } |
| return carrierName.toString(); |
| } |
| |
| } |