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