Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2014 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 package com.android.dialer.util;
     17 
     18 import android.app.AlertDialog;
     19 import android.content.ActivityNotFoundException;
     20 import android.content.Context;
     21 import android.content.DialogInterface;
     22 import android.content.DialogInterface.OnClickListener;
     23 import android.content.Intent;
     24 import android.content.SharedPreferences;
     25 import android.graphics.Point;
     26 import android.os.Build.VERSION;
     27 import android.os.Build.VERSION_CODES;
     28 import android.os.Bundle;
     29 import android.preference.PreferenceManager;
     30 import android.support.annotation.NonNull;
     31 import android.support.v4.content.ContextCompat;
     32 import android.telecom.TelecomManager;
     33 import android.telephony.TelephonyManager;
     34 import android.text.BidiFormatter;
     35 import android.text.TextDirectionHeuristics;
     36 import android.view.View;
     37 import android.view.inputmethod.InputMethodManager;
     38 import android.widget.Toast;
     39 import com.android.dialer.common.Assert;
     40 import com.android.dialer.common.LogUtil;
     41 import com.android.dialer.telecom.TelecomUtil;
     42 import java.io.File;
     43 import java.util.Iterator;
     44 import java.util.Random;
     45 
     46 /** General purpose utility methods for the Dialer. */
     47 public class DialerUtils {
     48 
     49   /**
     50    * Prefix on a dialed number that indicates that the call should be placed through the Wireless
     51    * Priority Service.
     52    */
     53   private static final String WPS_PREFIX = "*272";
     54 
     55   public static final String FILE_PROVIDER_CACHE_DIR = "my_cache";
     56 
     57   private static final Random RANDOM = new Random();
     58 
     59   /**
     60    * Attempts to start an activity and displays a toast with the default error message if the
     61    * activity is not found, instead of throwing an exception.
     62    *
     63    * @param context to start the activity with.
     64    * @param intent to start the activity with.
     65    */
     66   public static void startActivityWithErrorToast(Context context, Intent intent) {
     67     startActivityWithErrorToast(context, intent, R.string.activity_not_available);
     68   }
     69 
     70   /**
     71    * Attempts to start an activity and displays a toast with a provided error message if the
     72    * activity is not found, instead of throwing an exception.
     73    *
     74    * @param context to start the activity with.
     75    * @param intent to start the activity with.
     76    * @param msgId Resource ID of the string to display in an error message if the activity is not
     77    *     found.
     78    */
     79   public static void startActivityWithErrorToast(
     80       final Context context, final Intent intent, int msgId) {
     81     try {
     82       if ((Intent.ACTION_CALL.equals(intent.getAction()))) {
     83         // All dialer-initiated calls should pass the touch point to the InCallUI
     84         Point touchPoint = TouchPointManager.getInstance().getPoint();
     85         if (touchPoint.x != 0 || touchPoint.y != 0) {
     86           Bundle extras;
     87           // Make sure to not accidentally clobber any existing extras
     88           if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
     89             extras = intent.getParcelableExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
     90           } else {
     91             extras = new Bundle();
     92           }
     93           extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
     94           intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
     95         }
     96 
     97         if (shouldWarnForOutgoingWps(context, intent.getData().getSchemeSpecificPart())) {
     98           LogUtil.i(
     99               "DialUtils.startActivityWithErrorToast",
    100               "showing outgoing WPS dialog before placing call");
    101           AlertDialog.Builder builder = new AlertDialog.Builder(context);
    102           builder.setMessage(R.string.outgoing_wps_warning);
    103           builder.setPositiveButton(
    104               R.string.dialog_continue,
    105               new OnClickListener() {
    106                 @Override
    107                 public void onClick(DialogInterface dialog, int which) {
    108                   placeCallOrMakeToast(context, intent);
    109                 }
    110               });
    111           builder.setNegativeButton(android.R.string.cancel, null);
    112           builder.create().show();
    113         } else {
    114           placeCallOrMakeToast(context, intent);
    115         }
    116       } else {
    117         context.startActivity(intent);
    118       }
    119     } catch (ActivityNotFoundException e) {
    120       Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
    121     }
    122   }
    123 
    124   private static void placeCallOrMakeToast(Context context, Intent intent) {
    125     final boolean hasCallPermission = TelecomUtil.placeCall(context, intent);
    126     if (!hasCallPermission) {
    127       // TODO: Make calling activity show request permission dialog and handle
    128       // callback results appropriately.
    129       Toast.makeText(context, "Cannot place call without Phone permission", Toast.LENGTH_SHORT)
    130           .show();
    131     }
    132   }
    133 
    134   /**
    135    * Returns whether the user should be warned about an outgoing WPS call. This checks if there is a
    136    * currently active call over LTE. Regardless of the country or carrier, the radio will drop an
    137    * active LTE call if a WPS number is dialed, so this warning is necessary.
    138    */
    139   private static boolean shouldWarnForOutgoingWps(Context context, String number) {
    140     if (number != null && number.startsWith(WPS_PREFIX)) {
    141       TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
    142       boolean isOnVolte =
    143           VERSION.SDK_INT >= VERSION_CODES.N
    144               && telephonyManager.getVoiceNetworkType() == TelephonyManager.NETWORK_TYPE_LTE;
    145       boolean hasCurrentActiveCall =
    146           telephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
    147       return isOnVolte && hasCurrentActiveCall;
    148     }
    149     return false;
    150   }
    151 
    152   /**
    153    * Closes an {@link AutoCloseable}, silently ignoring any checked exceptions. Does nothing if
    154    * null.
    155    *
    156    * @param closeable to close.
    157    */
    158   public static void closeQuietly(AutoCloseable closeable) {
    159     if (closeable != null) {
    160       try {
    161         closeable.close();
    162       } catch (RuntimeException rethrown) {
    163         throw rethrown;
    164       } catch (Exception ignored) {
    165       }
    166     }
    167   }
    168 
    169   /**
    170    * Joins a list of {@link CharSequence} into a single {@link CharSequence} seperated by ", ".
    171    *
    172    * @param list List of char sequences to join.
    173    * @return Joined char sequences.
    174    */
    175   public static CharSequence join(Iterable<CharSequence> list) {
    176     StringBuilder sb = new StringBuilder();
    177     final BidiFormatter formatter = BidiFormatter.getInstance();
    178     final CharSequence separator = ", ";
    179 
    180     Iterator<CharSequence> itr = list.iterator();
    181     boolean firstTime = true;
    182     while (itr.hasNext()) {
    183       if (firstTime) {
    184         firstTime = false;
    185       } else {
    186         sb.append(separator);
    187       }
    188       // Unicode wrap the elements of the list to respect RTL for individual strings.
    189       sb.append(
    190           formatter.unicodeWrap(itr.next().toString(), TextDirectionHeuristics.FIRSTSTRONG_LTR));
    191     }
    192 
    193     // Unicode wrap the joined value, to respect locale's RTL ordering for the whole list.
    194     return formatter.unicodeWrap(sb.toString());
    195   }
    196 
    197   public static void showInputMethod(View view) {
    198     final InputMethodManager imm =
    199         (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    200     if (imm != null) {
    201       imm.showSoftInput(view, 0);
    202     }
    203   }
    204 
    205   public static void hideInputMethod(View view) {
    206     final InputMethodManager imm =
    207         (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    208     if (imm != null) {
    209       imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    210     }
    211   }
    212 
    213   /**
    214    * Create a File in the cache directory that Dialer's FileProvider knows about so they can be
    215    * shared to other apps.
    216    */
    217   public static File createShareableFile(Context context) {
    218     long fileId = Math.abs(RANDOM.nextLong());
    219     File parentDir = new File(context.getCacheDir(), FILE_PROVIDER_CACHE_DIR);
    220     if (!parentDir.exists()) {
    221       parentDir.mkdirs();
    222     }
    223     return new File(parentDir, String.valueOf(fileId));
    224   }
    225 
    226   /**
    227    * Returns default preference for context accessing device protected storage. This is used when
    228    * directBoot is enabled (before device unlocked after boot) since the default shared preference
    229    * used normally is not available at this moment for N devices. Returns regular default shared
    230    * preference for pre-N devices.
    231    */
    232   @NonNull
    233   public static SharedPreferences getDefaultSharedPreferenceForDeviceProtectedStorageContext(
    234       @NonNull Context context) {
    235     Assert.isNotNull(context);
    236     Context deviceProtectedContext =
    237         ContextCompat.isDeviceProtectedStorage(context)
    238             ? context
    239             : ContextCompat.createDeviceProtectedStorageContext(context);
    240     // ContextCompat.createDeviceProtectedStorageContext(context) returns null on pre-N, thus fall
    241     // back to regular default shared preference for pre-N devices since devices protected context
    242     // is not available.
    243     return PreferenceManager.getDefaultSharedPreferences(
    244         deviceProtectedContext != null ? deviceProtectedContext : context);
    245   }
    246 }
    247