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.Activity;
19 import android.content.ActivityNotFoundException;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.res.Resources;
26 import android.graphics.Point;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.provider.Telephony;
30 import android.telecom.TelecomManager;
31 import android.text.BidiFormatter;
32 import android.text.TextDirectionHeuristics;
33 import android.text.TextUtils;
34 import android.view.View;
35 import android.view.inputmethod.InputMethodManager;
36 import android.widget.Toast;
37 
38 import com.android.contacts.common.ContactsUtils;
39 import com.android.contacts.common.interactions.TouchPointManager;
40 import com.android.dialer.R;
41 
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Locale;
45 
46 /**
47  * General purpose utility methods for the Dialer.
48  */
49 public class DialerUtils {
50 
51     /**
52      * Attempts to start an activity and displays a toast with the default error message if the
53      * activity is not found, instead of throwing an exception.
54      *
55      * @param context to start the activity with.
56      * @param intent to start the activity with.
57      */
startActivityWithErrorToast(Context context, Intent intent)58     public static void startActivityWithErrorToast(Context context, Intent intent) {
59         startActivityWithErrorToast(context, intent, R.string.activity_not_available);
60     }
61 
62     /**
63      * Attempts to start an activity and displays a toast with a provided error message if the
64      * activity is not found, instead of throwing an exception.
65      *
66      * @param context to start the activity with.
67      * @param intent to start the activity with.
68      * @param msgId Resource ID of the string to display in an error message if the activity is
69      *              not found.
70      */
startActivityWithErrorToast(Context context, Intent intent, int msgId)71     public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) {
72         try {
73             if ((IntentUtil.CALL_ACTION.equals(intent.getAction())
74                             && context instanceof Activity)) {
75                 // All dialer-initiated calls should pass the touch point to the InCallUI
76                 Point touchPoint = TouchPointManager.getInstance().getPoint();
77                 if (touchPoint.x != 0 || touchPoint.y != 0) {
78                     Bundle extras;
79                     // Make sure to not accidentally clobber any existing extras
80                     if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
81                         extras = intent.getParcelableExtra(
82                                 TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
83                     } else {
84                         extras = new Bundle();
85                     }
86                     extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
87                     intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
88                 }
89 
90                 final boolean hasCallPermission = TelecomUtil.placeCall((Activity) context, intent);
91                 if (!hasCallPermission) {
92                     // TODO: Make calling activity show request permission dialog and handle
93                     // callback results appropriately.
94                     Toast.makeText(context, "Cannot place call without Phone permission",
95                             Toast.LENGTH_SHORT);
96                 }
97             } else {
98                 context.startActivity(intent);
99             }
100         } catch (ActivityNotFoundException e) {
101             Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
102         }
103     }
104 
105     /**
106      * Returns the component name to use in order to send an SMS using the default SMS application,
107      * or null if none exists.
108      */
getSmsComponent(Context context)109     public static ComponentName getSmsComponent(Context context) {
110         String smsPackage = Telephony.Sms.getDefaultSmsPackage(context);
111         if (smsPackage != null) {
112             final PackageManager packageManager = context.getPackageManager();
113             final Intent intent = new Intent(Intent.ACTION_SENDTO,
114                     Uri.fromParts(ContactsUtils.SCHEME_SMSTO, "", null));
115             final List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
116             for (ResolveInfo resolveInfo : resolveInfos) {
117                 if (smsPackage.equals(resolveInfo.activityInfo.packageName)) {
118                     return new ComponentName(smsPackage, resolveInfo.activityInfo.name);
119                 }
120             }
121         }
122         return null;
123     }
124 
125     /**
126      * Closes an {@link AutoCloseable}, silently ignoring any checked exceptions. Does nothing if
127      * null.
128      *
129      * @param closeable to close.
130      */
closeQuietly(AutoCloseable closeable)131     public static void closeQuietly(AutoCloseable closeable) {
132         if (closeable != null) {
133             try {
134                 closeable.close();
135             } catch (RuntimeException rethrown) {
136                 throw rethrown;
137             } catch (Exception ignored) {
138             }
139         }
140     }
141 
142     /**
143      * Joins a list of {@link CharSequence} into a single {@link CharSequence} seperated by a
144      * localized delimiter such as ", ".
145      *
146      * @param resources Resources used to get list delimiter.
147      * @param list List of char sequences to join.
148      * @return Joined char sequences.
149      */
join(Resources resources, Iterable<CharSequence> list)150     public static CharSequence join(Resources resources, Iterable<CharSequence> list) {
151         StringBuilder sb = new StringBuilder();
152         final BidiFormatter formatter = BidiFormatter.getInstance();
153         final CharSequence separator = resources.getString(R.string.list_delimeter);
154 
155         Iterator<CharSequence> itr = list.iterator();
156         boolean firstTime = true;
157         while (itr.hasNext()) {
158             if (firstTime) {
159                 firstTime = false;
160             } else {
161                 sb.append(separator);
162             }
163             // Unicode wrap the elements of the list to respect RTL for individual strings.
164             sb.append(formatter.unicodeWrap(
165                     itr.next().toString(), TextDirectionHeuristics.FIRSTSTRONG_LTR));
166         }
167 
168         // Unicode wrap the joined value, to respect locale's RTL ordering for the whole list.
169         return formatter.unicodeWrap(sb.toString());
170     }
171 
172     /**
173      * @return True if the application is currently in RTL mode.
174      */
isRtl()175     public static boolean isRtl() {
176         return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
177             View.LAYOUT_DIRECTION_RTL;
178     }
179 
showInputMethod(View view)180     public static void showInputMethod(View view) {
181         final InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(
182                 Context.INPUT_METHOD_SERVICE);
183         if (imm != null) {
184             imm.showSoftInput(view, 0);
185         }
186     }
187 
hideInputMethod(View view)188     public static void hideInputMethod(View view) {
189         final InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(
190                 Context.INPUT_METHOD_SERVICE);
191         if (imm != null) {
192             imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
193         }
194     }
195 }
196