• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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