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 */ startActivityWithErrorToast(Context context, Intent intent)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 */ startActivityWithErrorToast( final Context context, final Intent intent, int msgId)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 placeCallOrMakeToast(Context context, Intent intent)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 */ shouldWarnForOutgoingWps(Context context, String number)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 */ closeQuietly(AutoCloseable closeable)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 */ join(Iterable<CharSequence> list)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 showInputMethod(View view)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 hideInputMethod(View view)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 */ createShareableFile(Context context)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 getDefaultSharedPreferenceForDeviceProtectedStorageContext( @onNull Context context)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