Home | History | Annotate | Download | only in calling
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.wifi.calling;
     18 
     19 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
     20 
     21 import android.app.PendingIntent;
     22 import android.content.ComponentName;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.net.Uri;
     27 import android.os.PersistableBundle;
     28 import android.provider.Settings;
     29 import android.support.v4.graphics.drawable.IconCompat;
     30 import android.telephony.CarrierConfigManager;
     31 import android.telephony.SubscriptionManager;
     32 import android.telephony.TelephonyManager;
     33 import android.text.TextUtils;
     34 import android.util.Log;
     35 
     36 import androidx.slice.Slice;
     37 import androidx.slice.builders.ListBuilder;
     38 import androidx.slice.builders.SliceAction;
     39 
     40 import com.android.ims.ImsManager;
     41 import com.android.internal.annotations.VisibleForTesting;
     42 import com.android.settings.R;
     43 import com.android.settings.slices.SettingsSliceProvider;
     44 import com.android.settings.slices.SliceBroadcastReceiver;
     45 import com.android.settings.slices.SliceBuilderUtils;
     46 
     47 import java.util.concurrent.Callable;
     48 import java.util.concurrent.ExecutionException;
     49 import java.util.concurrent.ExecutorService;
     50 import java.util.concurrent.Executors;
     51 import java.util.concurrent.FutureTask;
     52 import java.util.concurrent.TimeUnit;
     53 import java.util.concurrent.TimeoutException;
     54 
     55 
     56 /**
     57  * Helper class to control slices for wifi calling settings.
     58  */
     59 public class WifiCallingSliceHelper {
     60 
     61     private static final String TAG = "WifiCallingSliceHelper";
     62 
     63     /**
     64      * Settings slice path to wifi calling setting.
     65      */
     66     public static final String PATH_WIFI_CALLING = "wifi_calling";
     67 
     68     /**
     69      * Action passed for changes to wifi calling slice (toggle).
     70      */
     71     public static final String ACTION_WIFI_CALLING_CHANGED =
     72             "com.android.settings.wifi.calling.action.WIFI_CALLING_CHANGED";
     73 
     74     /**
     75      * Action for Wifi calling Settings activity which
     76      * allows setting configuration for Wifi calling
     77      * related settings
     78      */
     79     public static final String ACTION_WIFI_CALLING_SETTINGS_ACTIVITY =
     80             "android.settings.WIFI_CALLING_SETTINGS";
     81 
     82     /**
     83      * Full {@link Uri} for the Wifi Calling Slice.
     84      */
     85     public static final Uri WIFI_CALLING_URI = new Uri.Builder()
     86             .scheme(ContentResolver.SCHEME_CONTENT)
     87             .authority(SettingsSliceProvider.SLICE_AUTHORITY)
     88             .appendPath(PATH_WIFI_CALLING)
     89             .build();
     90 
     91     /**
     92      * Timeout for querying wifi calling setting from ims manager.
     93      */
     94     private static final int TIMEOUT_MILLIS = 2000;
     95 
     96     protected SubscriptionManager mSubscriptionManager;
     97     private final Context mContext;
     98 
     99     @VisibleForTesting
    100     public WifiCallingSliceHelper(Context context) {
    101         mContext = context;
    102     }
    103 
    104     /**
    105      * Returns Slice object for wifi calling settings.
    106      *
    107      * If wifi calling is being turned on and if wifi calling activation is needed for the current
    108      * carrier, this method will return Slice with instructions to go to Settings App.
    109      *
    110      * If wifi calling is not supported for the current carrier, this method will return slice with
    111      * not supported message.
    112      *
    113      * If wifi calling setting can be changed, this method will return the slice to toggle wifi
    114      * calling option with ACTION_WIFI_CALLING_CHANGED as endItem.
    115      */
    116     public Slice createWifiCallingSlice(Uri sliceUri) {
    117         final int subId = getDefaultVoiceSubId();
    118         final String carrierName = getSimCarrierName();
    119 
    120         if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
    121             Log.d(TAG, "Invalid subscription Id");
    122             return getNonActionableWifiCallingSlice(
    123                     mContext.getString(R.string.wifi_calling_settings_title),
    124                     mContext.getString(R.string.wifi_calling_not_supported, carrierName),
    125                     sliceUri, getSettingsIntent(mContext));
    126         }
    127 
    128         final ImsManager imsManager = getImsManager(subId);
    129 
    130         if (!imsManager.isWfcEnabledByPlatform()
    131                 || !imsManager.isWfcProvisionedOnDevice()) {
    132             Log.d(TAG, "Wifi calling is either not provisioned or not enabled by Platform");
    133             return getNonActionableWifiCallingSlice(
    134                     mContext.getString(R.string.wifi_calling_settings_title),
    135                     mContext.getString(R.string.wifi_calling_not_supported, carrierName),
    136                     sliceUri, getSettingsIntent(mContext));
    137         }
    138 
    139         try {
    140             final boolean isWifiCallingEnabled = isWifiCallingEnabled(imsManager);
    141             final Intent activationAppIntent =
    142                     getWifiCallingCarrierActivityIntent(subId);
    143 
    144             // Send this actionable wifi calling slice to toggle the setting
    145             // only when there is no need for wifi calling activation with the server
    146             if (activationAppIntent != null && !isWifiCallingEnabled) {
    147                 Log.d(TAG, "Needs Activation");
    148                 // Activation needed for the next action of the user
    149                 // Give instructions to go to settings app
    150                 return getNonActionableWifiCallingSlice(
    151                         mContext.getString(R.string.wifi_calling_settings_title),
    152                         mContext.getString(
    153                                 R.string.wifi_calling_settings_activation_instructions),
    154                         sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY));
    155             }
    156             return getWifiCallingSlice(sliceUri, mContext, isWifiCallingEnabled);
    157         } catch (InterruptedException | TimeoutException | ExecutionException e) {
    158             Log.e(TAG, "Unable to read the current WiFi calling status", e);
    159             return getNonActionableWifiCallingSlice(
    160                     mContext.getString(R.string.wifi_calling_settings_title),
    161                     mContext.getString(R.string.wifi_calling_turn_on),
    162                     sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY));
    163         }
    164     }
    165 
    166     private boolean isWifiCallingEnabled(ImsManager imsManager)
    167             throws InterruptedException, ExecutionException, TimeoutException {
    168         final FutureTask<Boolean> isWifiOnTask = new FutureTask<>(new Callable<Boolean>() {
    169             @Override
    170             public Boolean call() {
    171                 return imsManager.isWfcEnabledByUser();
    172             }
    173         });
    174         final ExecutorService executor = Executors.newSingleThreadExecutor();
    175         executor.execute(isWifiOnTask);
    176 
    177         Boolean isWifiEnabledByUser = false;
    178         isWifiEnabledByUser = isWifiOnTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    179 
    180         return isWifiEnabledByUser && imsManager.isNonTtyOrTtyOnVolteEnabled();
    181     }
    182 
    183     /**
    184      * Builds a toggle slice where the intent takes you to the wifi calling page and the toggle
    185      * enables/disables wifi calling.
    186      */
    187     private Slice getWifiCallingSlice(Uri sliceUri, Context mContext,
    188             boolean isWifiCallingEnabled) {
    189 
    190         final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
    191         final String title = mContext.getString(R.string.wifi_calling_settings_title);
    192         return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
    193                 .setColor(R.color.material_blue_500)
    194                 .addRow(b -> b
    195                         .setTitle(title)
    196                         .addEndItem(
    197                                 new SliceAction(
    198                                         getBroadcastIntent(ACTION_WIFI_CALLING_CHANGED),
    199                                         null /* actionTitle */, isWifiCallingEnabled))
    200                         .setPrimaryAction(new SliceAction(
    201                                 getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY),
    202                                 icon,
    203                                 title)))
    204                 .build();
    205     }
    206 
    207     protected ImsManager getImsManager(int subId) {
    208         return ImsManager.getInstance(mContext, SubscriptionManager.getPhoneId(subId));
    209     }
    210 
    211     private Integer getWfcMode(ImsManager imsManager)
    212             throws InterruptedException, ExecutionException, TimeoutException {
    213         FutureTask<Integer> wfcModeTask = new FutureTask<>(new Callable<Integer>() {
    214             @Override
    215             public Integer call() {
    216                 return imsManager.getWfcMode(false);
    217             }
    218         });
    219         ExecutorService executor = Executors.newSingleThreadExecutor();
    220         executor.execute(wfcModeTask);
    221         return wfcModeTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    222     }
    223 
    224     /**
    225      * Handles wifi calling setting change from wifi calling slice and posts notification. Should be
    226      * called when intent action is ACTION_WIFI_CALLING_CHANGED. Executed in @WorkerThread
    227      *
    228      * @param intent action performed
    229      */
    230     public void handleWifiCallingChanged(Intent intent) {
    231         final int subId = getDefaultVoiceSubId();
    232 
    233         if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
    234             final ImsManager imsManager = getImsManager(subId);
    235             if (imsManager.isWfcEnabledByPlatform()
    236                     || imsManager.isWfcProvisionedOnDevice()) {
    237                 final boolean currentValue = imsManager.isWfcEnabledByUser()
    238                         && imsManager.isNonTtyOrTtyOnVolteEnabled();
    239                 final boolean newValue = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
    240                         currentValue);
    241                 final Intent activationAppIntent =
    242                         getWifiCallingCarrierActivityIntent(subId);
    243                 if (!newValue || activationAppIntent == null) {
    244                     // If either the action is to turn off wifi calling setting
    245                     // or there is no activation involved - Update the setting
    246                     if (newValue != currentValue) {
    247                         imsManager.setWfcSetting(newValue);
    248                     }
    249                 }
    250             }
    251         }
    252         // notify change in slice in any case to get re-queried. This would result in displaying
    253         // appropriate message with the updated setting.
    254         final Uri uri = SliceBuilderUtils.getUri(PATH_WIFI_CALLING, false /*isPlatformSlice*/);
    255         mContext.getContentResolver().notifyChange(uri, null);
    256     }
    257 
    258     /**
    259      * Returns Slice with the title and subtitle provided as arguments with wifi signal Icon.
    260      *
    261      * @param title Title of the slice
    262      * @param subtitle Subtitle of the slice
    263      * @param sliceUri slice uri
    264      * @return Slice with title and subtitle
    265      */
    266     // TODO(b/79548264) asses different scenarios and return null instead of non-actionable slice
    267     private Slice getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri,
    268             PendingIntent primaryActionIntent) {
    269         final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
    270         return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
    271                 .setColor(R.color.material_blue_500)
    272                 .addRow(b -> b
    273                         .setTitle(title)
    274                         .setSubtitle(subtitle)
    275                         .setPrimaryAction(new SliceAction(
    276                                 primaryActionIntent, icon,
    277                                 title)))
    278                 .build();
    279     }
    280 
    281     /**
    282      * Returns {@code true} when the key is enabled for the carrier, and {@code false} otherwise.
    283      */
    284     private boolean isCarrierConfigManagerKeyEnabled(Context mContext, String key,
    285             int subId, boolean defaultValue) {
    286         final CarrierConfigManager configManager = getCarrierConfigManager(mContext);
    287         boolean ret = false;
    288         if (configManager != null) {
    289             final PersistableBundle bundle = configManager.getConfigForSubId(subId);
    290             if (bundle != null) {
    291                 ret = bundle.getBoolean(key, defaultValue);
    292             }
    293         }
    294         return ret;
    295     }
    296 
    297     protected CarrierConfigManager getCarrierConfigManager(Context mContext) {
    298         return mContext.getSystemService(CarrierConfigManager.class);
    299     }
    300 
    301     /**
    302      * Returns the current default voice subId obtained from SubscriptionManager
    303      */
    304     protected int getDefaultVoiceSubId() {
    305         if (mSubscriptionManager == null) {
    306             mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
    307         }
    308         return SubscriptionManager.getDefaultVoiceSubscriptionId();
    309     }
    310 
    311     /**
    312      * Returns Intent of the activation app required to activate wifi calling or null if there is no
    313      * need for activation.
    314      */
    315     protected Intent getWifiCallingCarrierActivityIntent(int subId) {
    316         final CarrierConfigManager configManager = getCarrierConfigManager(mContext);
    317         if (configManager == null) {
    318             return null;
    319         }
    320 
    321         final PersistableBundle bundle = configManager.getConfigForSubId(subId);
    322         if (bundle == null) {
    323             return null;
    324         }
    325 
    326         final String carrierApp = bundle.getString(
    327                 CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING);
    328         if (TextUtils.isEmpty(carrierApp)) {
    329             return null;
    330         }
    331 
    332         final ComponentName componentName = ComponentName.unflattenFromString(carrierApp);
    333         if (componentName == null) {
    334             return null;
    335         }
    336 
    337         final Intent intent = new Intent();
    338         intent.setComponent(componentName);
    339         return intent;
    340     }
    341 
    342     /**
    343      * @return {@link PendingIntent} to the Settings home page.
    344      */
    345     public static PendingIntent getSettingsIntent(Context context) {
    346         final Intent intent = new Intent(Settings.ACTION_SETTINGS);
    347         return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
    348     }
    349 
    350     private PendingIntent getBroadcastIntent(String action) {
    351         final Intent intent = new Intent(action);
    352         intent.setClass(mContext, SliceBroadcastReceiver.class);
    353         return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
    354                 PendingIntent.FLAG_CANCEL_CURRENT);
    355     }
    356 
    357     /**
    358      * Returns PendingIntent to start activity specified by action
    359      */
    360     private PendingIntent getActivityIntent(String action) {
    361         final Intent intent = new Intent(action);
    362         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    363         return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */);
    364     }
    365 
    366     /**
    367      * Returns carrier id name of the current Subscription
    368      */
    369     private String getSimCarrierName() {
    370         final TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
    371         final CharSequence carrierName = telephonyManager.getSimCarrierIdName();
    372         if (carrierName == null) {
    373             return mContext.getString(R.string.carrier);
    374         }
    375         return carrierName.toString();
    376     }
    377 
    378 }
    379