blob: ffce899c5857977e79c33b5ce4d12cf9da9c021b [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
2 * Copyright (C) 2011 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
17package com.android.phone;
18
19import android.app.ActivityManager;
20import android.app.ActionBar;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.SharedPreferences;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager;
32import android.content.pm.ResolveInfo;
33import android.content.pm.ServiceInfo;
34import android.content.res.Resources;
35import android.graphics.drawable.Drawable;
36import android.net.Uri;
37import android.os.Bundle;
38import android.os.SystemProperties;
39import android.preference.EditTextPreference;
40import android.preference.Preference;
41import android.preference.PreferenceActivity;
42import android.telephony.PhoneNumberUtils;
43import android.telephony.TelephonyManager;
44import android.text.TextUtils;
45import android.util.Log;
46import android.view.LayoutInflater;
47import android.view.Menu;
48import android.view.MenuItem;
49import android.view.View;
50import android.view.ViewGroup;
51import android.widget.AdapterView;
52import android.widget.ArrayAdapter;
53import android.widget.BaseAdapter;
54import android.widget.CheckBox;
55import android.widget.CompoundButton;
56import android.widget.ImageView;
57import android.widget.ListView;
58import android.widget.TextView;
59import android.widget.Toast;
60
61import com.android.internal.telephony.Call;
62import com.android.internal.telephony.Connection;
63import com.android.internal.telephony.PhoneConstants;
Yorke Lee814da302013-08-30 16:01:07 -070064
Santos Cordon7d4ddf62013-07-10 11:58:08 -070065import com.google.android.collect.Lists;
66
67import java.util.ArrayList;
68import java.util.Arrays;
69import java.util.List;
70
71/**
72 * Helper class to manage the "Respond via Message" feature for incoming calls.
73 *
74 * @see InCallScreen.internalRespondViaSms()
75 */
76public class RespondViaSmsManager {
77 private static final String TAG = "RespondViaSmsManager";
78 private static final boolean DBG =
79 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
80 // Do not check in with VDBG = true, since that may write PII to the system log.
81 private static final boolean VDBG = false;
82
83 private static final String PERMISSION_SEND_RESPOND_VIA_MESSAGE =
84 "android.permission.SEND_RESPOND_VIA_MESSAGE";
85
86 private int mIconSize = -1;
87
88 /**
89 * Reference to the InCallScreen activity that owns us. This may be
90 * null if we haven't been initialized yet *or* after the InCallScreen
91 * activity has been destroyed.
92 */
93 private InCallScreen mInCallScreen;
94
95 /**
96 * The popup showing the list of canned responses.
97 *
98 * This is an AlertDialog containing a ListView showing the possible
99 * choices. This may be null if the InCallScreen hasn't ever called
100 * showRespondViaSmsPopup() yet, or if the popup was visible once but
101 * then got dismissed.
102 */
103 private Dialog mCannedResponsePopup;
104
105 /**
106 * The popup dialog allowing the user to chose which app handles respond-via-sms.
107 *
108 * An AlertDialog showing the Resolve-App UI resource from the framework wchih we then fill in
109 * with the appropriate data set. Can be null when not visible.
110 */
111 private Dialog mPackageSelectionPopup;
112
113 /** The array of "canned responses"; see loadCannedResponses(). */
114 private String[] mCannedResponses;
115
116 /** SharedPreferences file name for our persistent settings. */
117 private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs";
118
119 // Preference keys for the 4 "canned responses"; see RespondViaSmsManager$Settings.
120 // Since (for now at least) the number of messages is fixed at 4, and since
121 // SharedPreferences can't deal with arrays anyway, just store the messages
122 // as 4 separate strings.
123 private static final int NUM_CANNED_RESPONSES = 4;
124 private static final String KEY_CANNED_RESPONSE_PREF_1 = "canned_response_pref_1";
125 private static final String KEY_CANNED_RESPONSE_PREF_2 = "canned_response_pref_2";
126 private static final String KEY_CANNED_RESPONSE_PREF_3 = "canned_response_pref_3";
127 private static final String KEY_CANNED_RESPONSE_PREF_4 = "canned_response_pref_4";
128 private static final String KEY_PREFERRED_PACKAGE = "preferred_package_pref";
129 private static final String KEY_INSTANT_TEXT_DEFAULT_COMPONENT = "instant_text_def_component";
130
131 /**
132 * RespondViaSmsManager constructor.
133 */
134 public RespondViaSmsManager() {
135 }
136
137 public void setInCallScreenInstance(InCallScreen inCallScreen) {
138 mInCallScreen = inCallScreen;
139
140 if (mInCallScreen != null) {
141 // Prefetch shared preferences to make the first canned response lookup faster
142 // (and to prevent StrictMode violation)
143 mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
144 }
145 }
146
147 /**
148 * Brings up the "Respond via SMS" popup for an incoming call.
149 *
150 * @param ringingCall the current incoming call
151 */
152 public void showRespondViaSmsPopup(Call ringingCall) {
153 if (DBG) log("showRespondViaSmsPopup()...");
154
155 // Very quick succession of clicks can cause this to run twice.
156 // Stop here to avoid creating more than one popup.
157 if (isShowingPopup()) {
158 if (DBG) log("Skip showing popup when one is already shown.");
159 return;
160 }
161
162 ListView lv = new ListView(mInCallScreen);
163
164 // Refresh the array of "canned responses".
165 mCannedResponses = loadCannedResponses();
166
167 // Build the list: start with the canned responses, but manually add
168 // the write-your-own option as the last choice.
169 int numPopupItems = mCannedResponses.length + 1;
170 String[] popupItems = Arrays.copyOf(mCannedResponses, numPopupItems);
171 popupItems[numPopupItems - 1] = mInCallScreen.getResources()
172 .getString(R.string.respond_via_sms_custom_message);
173
174 ArrayAdapter<String> adapter =
175 new ArrayAdapter<String>(mInCallScreen,
176 android.R.layout.simple_list_item_1,
177 android.R.id.text1,
178 popupItems);
179 lv.setAdapter(adapter);
180
181 // Create a RespondViaSmsItemClickListener instance to handle item
182 // clicks from the popup.
183 // (Note we create a fresh instance for each incoming call, and
184 // stash away the call's phone number, since we can't necessarily
185 // assume this call will still be ringing when the user finally
186 // chooses a response.)
187
188 Connection c = ringingCall.getLatestConnection();
189 if (VDBG) log("- connection: " + c);
190
191 if (c == null) {
192 // Uh oh -- the "ringingCall" doesn't have any connections any more.
193 // (In other words, it's no longer ringing.) This is rare, but can
194 // happen if the caller hangs up right at the exact moment the user
195 // selects the "Respond via SMS" option.
196 // There's nothing to do here (since the incoming call is gone),
197 // so just bail out.
198 Log.i(TAG, "showRespondViaSmsPopup: null connection; bailing out...");
199 return;
200 }
201
202 // TODO: at this point we probably should re-check c.getAddress()
203 // and c.getNumberPresentation() for validity. (i.e. recheck the
204 // same cases in InCallTouchUi.showIncomingCallWidget() where we
205 // should have disallowed the "respond via SMS" feature in the
206 // first place.)
207
208 String phoneNumber = c.getAddress();
209 if (VDBG) log("- phoneNumber: " + phoneNumber);
210 lv.setOnItemClickListener(new RespondViaSmsItemClickListener(phoneNumber));
211
212 AlertDialog.Builder builder = new AlertDialog.Builder(mInCallScreen)
213 .setCancelable(true)
214 .setOnCancelListener(new RespondViaSmsCancelListener())
215 .setView(lv);
216 mCannedResponsePopup = builder.create();
217 mCannedResponsePopup.show();
218 }
219
220 /**
221 * Dismiss currently visible popups.
222 *
223 * This is safe to call even if the popup is already dismissed, and
224 * even if you never called showRespondViaSmsPopup() in the first
225 * place.
226 */
227 public void dismissPopup() {
228 if (mCannedResponsePopup != null) {
229 mCannedResponsePopup.dismiss(); // safe even if already dismissed
230 mCannedResponsePopup = null;
231 }
232 if (mPackageSelectionPopup != null) {
233 mPackageSelectionPopup.dismiss();
234 mPackageSelectionPopup = null;
235 }
236 }
237
238 public boolean isShowingPopup() {
239 return (mCannedResponsePopup != null && mCannedResponsePopup.isShowing())
240 || (mPackageSelectionPopup != null && mPackageSelectionPopup.isShowing());
241 }
242
243 /**
244 * OnItemClickListener for the "Respond via SMS" popup.
245 */
246 public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener {
247 // Phone number to send the SMS to.
248 private String mPhoneNumber;
249
250 public RespondViaSmsItemClickListener(String phoneNumber) {
251 mPhoneNumber = phoneNumber;
252 }
253
254 /**
255 * Handles the user selecting an item from the popup.
256 */
257 @Override
258 public void onItemClick(AdapterView<?> parent, // The ListView
259 View view, // The TextView that was clicked
260 int position,
261 long id) {
262 if (DBG) log("RespondViaSmsItemClickListener.onItemClick(" + position + ")...");
263 String message = (String) parent.getItemAtPosition(position);
264 if (VDBG) log("- message: '" + message + "'");
265
266 // The "Custom" choice is a special case.
267 // (For now, it's guaranteed to be the last item.)
268 if (position == (parent.getCount() - 1)) {
269 // Take the user to the standard SMS compose UI.
270 launchSmsCompose(mPhoneNumber);
271 onPostMessageSent();
272 } else {
273 sendTextToDefaultActivity(mPhoneNumber, message);
274 }
275 }
276 }
277
278
279 /**
280 * OnCancelListener for the "Respond via SMS" popup.
281 */
282 public class RespondViaSmsCancelListener implements DialogInterface.OnCancelListener {
283 public RespondViaSmsCancelListener() {
284 }
285
286 /**
287 * Handles the user canceling the popup, either by touching
288 * outside the popup or by pressing Back.
289 */
290 @Override
291 public void onCancel(DialogInterface dialog) {
292 if (DBG) log("RespondViaSmsCancelListener.onCancel()...");
293
294 dismissPopup();
295
296 final PhoneConstants.State state = PhoneGlobals.getInstance().mCM.getState();
297 if (state == PhoneConstants.State.IDLE) {
298 // This means the incoming call is already hung up when the user chooses not to
299 // use "Respond via SMS" feature. Let's just exit the whole in-call screen.
300 PhoneGlobals.getInstance().dismissCallScreen();
301 } else {
302
303 // If the user cancels the popup, this presumably means that
304 // they didn't actually mean to bring up the "Respond via SMS"
305 // UI in the first place (and instead want to go back to the
306 // state where they can either answer or reject the call.)
307 // So restart the ringer and bring back the regular incoming
308 // call UI.
309
310 // This will have no effect if the incoming call isn't still ringing.
311 PhoneGlobals.getInstance().notifier.restartRinger();
312
313 // We hid the GlowPadView widget way back in
314 // InCallTouchUi.onTrigger(), when the user first selected
315 // the "SMS" trigger.
316 //
317 // To bring it back, just force the entire InCallScreen to
318 // update itself based on the current telephony state.
319 // (Assuming the incoming call is still ringing, this will
320 // cause the incoming call widget to reappear.)
321 mInCallScreen.requestUpdateScreen();
322 }
323 }
324 }
325
326 private void sendTextToDefaultActivity(String phoneNumber, String message) {
327 if (DBG) log("sendTextToDefaultActivity()...");
328 final PackageManager packageManager = mInCallScreen.getPackageManager();
329
330 // Check to see if the default component to receive this intent is already saved
331 // and check to see if it still has the corrent permissions.
332 final SharedPreferences prefs = mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME,
333 Context.MODE_PRIVATE);
334 final String flattenedName = prefs.getString(KEY_INSTANT_TEXT_DEFAULT_COMPONENT, null);
335 if (flattenedName != null) {
336 if (DBG) log("Default package was found." + flattenedName);
337
338 final ComponentName componentName = ComponentName.unflattenFromString(flattenedName);
339 ServiceInfo serviceInfo = null;
340 try {
341 serviceInfo = packageManager.getServiceInfo(componentName, 0);
342 } catch (PackageManager.NameNotFoundException e) {
343 Log.w(TAG, "Default service does not have permission.");
344 }
345
346 if (serviceInfo != null &&
347 PERMISSION_SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
348 sendTextAndExit(phoneNumber, message, componentName, false);
349 return;
350 } else {
351 SharedPreferences.Editor editor = prefs.edit();
352 editor.remove(KEY_INSTANT_TEXT_DEFAULT_COMPONENT);
353 editor.apply();
354 }
355 }
356
357 final ArrayList<ComponentName> componentsWithPermission =
358 getPackagesWithInstantTextPermission();
359
360 final int size = componentsWithPermission.size();
361 if (size == 0) {
362 Log.e(TAG, "No appropriate package receiving the Intent. Don't send anything");
363 onPostMessageSent();
364 } else if (size == 1) {
365 sendTextAndExit(phoneNumber, message, componentsWithPermission.get(0), false);
366 } else {
367 showPackageSelectionDialog(phoneNumber, message, componentsWithPermission);
368 }
369 }
370
371 /**
372 * Queries the System to determine what packages contain services that can handle the instant
373 * text response Action AND have permissions to do so.
374 */
375 private ArrayList<ComponentName> getPackagesWithInstantTextPermission() {
376 PackageManager packageManager = mInCallScreen.getPackageManager();
377
378 ArrayList<ComponentName> componentsWithPermission = Lists.newArrayList();
379
380 // Get list of all services set up to handle the Instant Text intent.
381 final List<ResolveInfo> infos = packageManager.queryIntentServices(
382 getInstantTextIntent("", null, null), 0);
383
384 // Collect all the valid services
385 for (ResolveInfo resolveInfo : infos) {
386 final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
387 if (serviceInfo == null) {
388 Log.w(TAG, "Ignore package without proper service.");
389 continue;
390 }
391
392 // A Service is valid only if it requires the permission
393 // PERMISSION_SEND_RESPOND_VIA_MESSAGE
394 if (PERMISSION_SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
395 componentsWithPermission.add(new ComponentName(serviceInfo.packageName,
396 serviceInfo.name));
397 }
398 }
399
400 return componentsWithPermission;
401 }
402
403 private void showPackageSelectionDialog(String phoneNumber, String message,
404 List<ComponentName> components) {
405 if (DBG) log("showPackageSelectionDialog()...");
406
407 dismissPopup();
408
409 BaseAdapter adapter = new PackageSelectionAdapter(mInCallScreen, components);
410
411 PackageClickListener clickListener =
412 new PackageClickListener(phoneNumber, message, components);
413
414 final CharSequence title = mInCallScreen.getResources().getText(
415 com.android.internal.R.string.whichApplication);
416 LayoutInflater inflater =
417 (LayoutInflater) mInCallScreen.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
418
419 final View view = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
420 final CheckBox alwaysUse = (CheckBox) view.findViewById(
421 com.android.internal.R.id.alwaysUse);
422 alwaysUse.setText(com.android.internal.R.string.alwaysUse);
423 alwaysUse.setOnCheckedChangeListener(clickListener);
424
425 AlertDialog.Builder builder = new AlertDialog.Builder(mInCallScreen)
426 .setTitle(title)
427 .setCancelable(true)
428 .setOnCancelListener(new RespondViaSmsCancelListener())
429 .setAdapter(adapter, clickListener)
430 .setView(view);
431 mPackageSelectionPopup = builder.create();
432 mPackageSelectionPopup.show();
433 }
434
435 private class PackageSelectionAdapter extends BaseAdapter {
436 private final LayoutInflater mInflater;
437 private final List<ComponentName> mComponents;
438
439 public PackageSelectionAdapter(Context context, List<ComponentName> components) {
440 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
441 mComponents = components;
442 }
443
444 @Override
445 public int getCount() {
446 return mComponents.size();
447 }
448
449 @Override
450 public Object getItem(int position) {
451 return mComponents.get(position);
452 }
453
454 @Override
455 public long getItemId(int position) {
456 return position;
457 }
458
459 @Override
460 public View getView(int position, View convertView, ViewGroup parent) {
461 if (convertView == null) {
462 convertView = mInflater.inflate(
463 com.android.internal.R.layout.resolve_list_item, parent, false);
464 }
465
466 final ComponentName component = mComponents.get(position);
467 final String packageName = component.getPackageName();
468 final PackageManager packageManager = mInCallScreen.getPackageManager();
469
470 // Set the application label
471 final TextView text = (TextView) convertView.findViewById(
472 com.android.internal.R.id.text1);
473 final TextView text2 = (TextView) convertView.findViewById(
474 com.android.internal.R.id.text2);
475
476 // Reset any previous values
477 text.setText("");
478 text2.setVisibility(View.GONE);
479 try {
480 final ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
481 final CharSequence label = packageManager.getApplicationLabel(appInfo);
482 if (label != null) {
483 text.setText(label);
484 }
485 } catch (PackageManager.NameNotFoundException e) {
486 Log.w(TAG, "Failed to load app label because package was not found.");
487 }
488
489 // Set the application icon
490 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
491 Drawable drawable = null;
492 try {
493 drawable = mInCallScreen.getPackageManager().getApplicationIcon(packageName);
494 } catch (PackageManager.NameNotFoundException e) {
495 Log.w(TAG, "Failed to load icon because it wasn't found.");
496 }
497 if (drawable == null) {
498 drawable = mInCallScreen.getPackageManager().getDefaultActivityIcon();
499 }
500 icon.setImageDrawable(drawable);
501 ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) icon.getLayoutParams();
502 lp.width = lp.height = getIconSize();
503
504 return convertView;
505 }
506
507 }
508
509 private class PackageClickListener implements DialogInterface.OnClickListener,
510 CompoundButton.OnCheckedChangeListener {
511 /** Phone number to send the SMS to. */
512 final private String mPhoneNumber;
513 final private String mMessage;
514 final private List<ComponentName> mComponents;
515 private boolean mMakeDefault = false;
516
517 public PackageClickListener(String phoneNumber, String message,
518 List<ComponentName> components) {
519 mPhoneNumber = phoneNumber;
520 mMessage = message;
521 mComponents = components;
522 }
523
524 @Override
525 public void onClick(DialogInterface dialog, int which) {
526 ComponentName component = mComponents.get(which);
527 sendTextAndExit(mPhoneNumber, mMessage, component, mMakeDefault);
528 }
529
530 @Override
531 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
532 Log.i(TAG, "mMakeDefault : " + isChecked);
533 mMakeDefault = isChecked;
534 }
535 }
536
537 private void sendTextAndExit(String phoneNumber, String message, ComponentName component,
538 boolean setDefaultComponent) {
539 // Send the selected message immediately with no user interaction.
540 sendText(phoneNumber, message, component);
541
542 if (setDefaultComponent) {
543 final SharedPreferences prefs = mInCallScreen.getSharedPreferences(
544 SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
545 prefs.edit()
546 .putString(KEY_INSTANT_TEXT_DEFAULT_COMPONENT, component.flattenToString())
547 .apply();
548 }
549
550 // ...and show a brief confirmation to the user (since
551 // otherwise it's hard to be sure that anything actually
552 // happened.)
553 final Resources res = mInCallScreen.getResources();
554 final String formatString = res.getString(R.string.respond_via_sms_confirmation_format);
555 final String confirmationMsg = String.format(formatString, phoneNumber);
556 Toast.makeText(mInCallScreen,
557 confirmationMsg,
558 Toast.LENGTH_LONG).show();
559
560 // TODO: If the device is locked, this toast won't actually ever
561 // be visible! (That's because we're about to dismiss the call
562 // screen, which means that the device will return to the
563 // keyguard. But toasts aren't visible on top of the keyguard.)
564 // Possible fixes:
565 // (1) Is it possible to allow a specific Toast to be visible
566 // on top of the keyguard?
567 // (2) Artifically delay the dismissCallScreen() call by 3
568 // seconds to allow the toast to be seen?
569 // (3) Don't use a toast at all; instead use a transient state
570 // of the InCallScreen (perhaps via the InCallUiState
571 // progressIndication feature), and have that state be
572 // visible for 3 seconds before calling dismissCallScreen().
573
574 onPostMessageSent();
575 }
576
577 /**
578 * Sends a text message without any interaction from the user.
579 */
580 private void sendText(String phoneNumber, String message, ComponentName component) {
581 if (VDBG) log("sendText: number "
582 + phoneNumber + ", message '" + message + "'");
583
584 mInCallScreen.startService(getInstantTextIntent(phoneNumber, message, component));
585 }
586
587 private void onPostMessageSent() {
588 // At this point the user is done dealing with the incoming call, so
589 // there's no reason to keep it around. (It's also confusing for
590 // the "incoming call" icon in the status bar to still be visible.)
591 // So reject the call now.
592 mInCallScreen.hangupRingingCall();
593
594 dismissPopup();
595
596 final PhoneConstants.State state = PhoneGlobals.getInstance().mCM.getState();
597 if (state == PhoneConstants.State.IDLE) {
598 // There's no other phone call to interact. Exit the entire in-call screen.
599 PhoneGlobals.getInstance().dismissCallScreen();
600 } else {
601 // The user is still in the middle of other phone calls, so we should keep the
602 // in-call screen.
603 mInCallScreen.requestUpdateScreen();
604 }
605 }
606
607 /**
608 * Brings up the standard SMS compose UI.
609 */
610 private void launchSmsCompose(String phoneNumber) {
611 if (VDBG) log("launchSmsCompose: number " + phoneNumber);
612
613 Intent intent = getInstantTextIntent(phoneNumber, null, null);
614
615 if (VDBG) log("- Launching SMS compose UI: " + intent);
616 mInCallScreen.startService(intent);
617 }
618
619 /**
620 * @param phoneNumber Must not be null.
621 * @param message Can be null. If message is null, the returned Intent will be configured to
622 * launch the SMS compose UI. If non-null, the returned Intent will cause the specified message
623 * to be sent with no interaction from the user.
624 * @param component The component that should handle this intent.
625 * @return Service Intent for the instant response.
626 */
627 private static Intent getInstantTextIntent(String phoneNumber, String message,
628 ComponentName component) {
629 final Uri uri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
630 Intent intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, uri);
631 if (message != null) {
632 intent.putExtra(Intent.EXTRA_TEXT, message);
633 } else {
634 intent.putExtra("exit_on_sent", true);
635 intent.putExtra("showUI", true);
636 }
637 if (component != null) {
638 intent.setComponent(component);
639 }
640 return intent;
641 }
642
643 /**
644 * Settings activity under "Call settings" to let you manage the
645 * canned responses; see respond_via_sms_settings.xml
646 */
647 public static class Settings extends PreferenceActivity
648 implements Preference.OnPreferenceChangeListener {
649 @Override
650 protected void onCreate(Bundle icicle) {
651 super.onCreate(icicle);
652 if (DBG) log("Settings: onCreate()...");
653
654 getPreferenceManager().setSharedPreferencesName(SHARED_PREFERENCES_NAME);
655
656 // This preference screen is ultra-simple; it's just 4 plain
657 // <EditTextPreference>s, one for each of the 4 "canned responses".
658 //
659 // The only nontrivial thing we do here is copy the text value of
660 // each of those EditTextPreferences and use it as the preference's
661 // "title" as well, so that the user will immediately see all 4
662 // strings when they arrive here.
663 //
664 // Also, listen for change events (since we'll need to update the
665 // title any time the user edits one of the strings.)
666
667 addPreferencesFromResource(R.xml.respond_via_sms_settings);
668
669 EditTextPreference pref;
670 pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_1);
671 pref.setTitle(pref.getText());
672 pref.setOnPreferenceChangeListener(this);
673
674 pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_2);
675 pref.setTitle(pref.getText());
676 pref.setOnPreferenceChangeListener(this);
677
678 pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_3);
679 pref.setTitle(pref.getText());
680 pref.setOnPreferenceChangeListener(this);
681
682 pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_4);
683 pref.setTitle(pref.getText());
684 pref.setOnPreferenceChangeListener(this);
685
686 ActionBar actionBar = getActionBar();
687 if (actionBar != null) {
688 // android.R.id.home will be triggered in onOptionsItemSelected()
689 actionBar.setDisplayHomeAsUpEnabled(true);
690 }
691 }
692
693 // Preference.OnPreferenceChangeListener implementation
694 @Override
695 public boolean onPreferenceChange(Preference preference, Object newValue) {
696 if (DBG) log("onPreferenceChange: key = " + preference.getKey());
697 if (VDBG) log(" preference = '" + preference + "'");
698 if (VDBG) log(" newValue = '" + newValue + "'");
699
700 EditTextPreference pref = (EditTextPreference) preference;
701
702 // Copy the new text over to the title, just like in onCreate().
703 // (Watch out: onPreferenceChange() is called *before* the
704 // Preference itself gets updated, so we need to use newValue here
705 // rather than pref.getText().)
706 pref.setTitle((String) newValue);
707
708 return true; // means it's OK to update the state of the Preference with the new value
709 }
710
711 @Override
712 public boolean onOptionsItemSelected(MenuItem item) {
713 final int itemId = item.getItemId();
714 switch (itemId) {
715 case android.R.id.home:
716 // See ActionBar#setDisplayHomeAsUpEnabled()
717 CallFeaturesSetting.goUpToTopLevelSetting(this);
718 return true;
719 case R.id.respond_via_message_reset:
720 // Reset the preferences settings
721 SharedPreferences prefs = getSharedPreferences(
722 SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
723 SharedPreferences.Editor editor = prefs.edit();
724 editor.remove(KEY_INSTANT_TEXT_DEFAULT_COMPONENT);
725 editor.apply();
726
727 return true;
728 default:
729 }
730 return super.onOptionsItemSelected(item);
731 }
732
733 @Override
734 public boolean onCreateOptionsMenu(Menu menu) {
735 getMenuInflater().inflate(R.menu.respond_via_message_settings_menu, menu);
736 return super.onCreateOptionsMenu(menu);
737 }
738 }
739
740 /**
741 * Read the (customizable) canned responses from SharedPreferences,
742 * or from defaults if the user has never actually brought up
743 * the Settings UI.
744 *
745 * This method does disk I/O (reading the SharedPreferences file)
746 * so don't call it from the main thread.
747 *
748 * @see RespondViaSmsManager.Settings
749 */
750 private String[] loadCannedResponses() {
751 if (DBG) log("loadCannedResponses()...");
752
753 SharedPreferences prefs = mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME,
754 Context.MODE_PRIVATE);
755 final Resources res = mInCallScreen.getResources();
756
757 String[] responses = new String[NUM_CANNED_RESPONSES];
758
759 // Note the default values here must agree with the corresponding
760 // android:defaultValue attributes in respond_via_sms_settings.xml.
761
762 responses[0] = prefs.getString(KEY_CANNED_RESPONSE_PREF_1,
763 res.getString(R.string.respond_via_sms_canned_response_1));
764 responses[1] = prefs.getString(KEY_CANNED_RESPONSE_PREF_2,
765 res.getString(R.string.respond_via_sms_canned_response_2));
766 responses[2] = prefs.getString(KEY_CANNED_RESPONSE_PREF_3,
767 res.getString(R.string.respond_via_sms_canned_response_3));
768 responses[3] = prefs.getString(KEY_CANNED_RESPONSE_PREF_4,
769 res.getString(R.string.respond_via_sms_canned_response_4));
770 return responses;
771 }
772
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700773 private int getIconSize() {
774 if (mIconSize < 0) {
775 final ActivityManager am =
776 (ActivityManager) mInCallScreen.getSystemService(Context.ACTIVITY_SERVICE);
777 mIconSize = am.getLauncherLargeIconSize();
778 }
779
780 return mIconSize;
781 }
782
783
784 private static void log(String msg) {
785 Log.d(TAG, msg);
786 }
787}