1/*
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.server.telecom;
18
19// TODO: Needed for move to system service: import com.android.internal.R;
20import com.android.internal.os.SomeArgs;
21import com.android.internal.telephony.SmsApplication;
22
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.SharedPreferences;
26import android.content.res.Resources;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.telecom.Connection;
31import android.telecom.Log;
32import android.telecom.Response;
33import android.telephony.PhoneNumberUtils;
34import android.telephony.SmsManager;
35import android.telephony.SubscriptionManager;
36import android.text.Spannable;
37import android.text.SpannableString;
38import android.text.TextUtils;
39import android.widget.Toast;
40
41import java.util.ArrayList;
42import java.util.List;
43
44/**
45 * Helper class to manage the "Respond via Message" feature for incoming calls.
46 */
47public class RespondViaSmsManager extends CallsManagerListenerBase {
48    private static final int MSG_SHOW_SENT_TOAST = 2;
49
50    private final CallsManager mCallsManager;
51    private final TelecomSystem.SyncRoot mLock;
52
53    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
54        @Override
55        public void handleMessage(Message msg) {
56            switch (msg.what) {
57                case MSG_SHOW_SENT_TOAST: {
58                    SomeArgs args = (SomeArgs) msg.obj;
59                    try {
60                        String toastMessage = (String) args.arg1;
61                        Context context = (Context) args.arg2;
62                        showMessageSentToast(toastMessage, context);
63                    } finally {
64                        args.recycle();
65                    }
66                    break;
67                }
68            }
69        }
70    };
71
72    public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) {
73        mCallsManager = callsManager;
74        mLock = lock;
75    }
76
77    /**
78     * Read the (customizable) canned responses from SharedPreferences,
79     * or from defaults if the user has never actually brought up
80     * the Settings UI.
81     *
82     * The interface of this method is asynchronous since it does disk I/O.
83     *
84     * @param response An object to receive an async reply, which will be called from
85     *                 the main thread.
86     * @param context The context.
87     */
88    public void loadCannedTextMessages(final Response<Void, List<String>> response,
89            final Context context) {
90        new Thread() {
91            @Override
92            public void run() {
93                Log.d(RespondViaSmsManager.this, "loadCannedResponses() starting");
94
95                // This function guarantees that QuickResponses will be in our
96                // SharedPreferences with the proper values considering there may be
97                // old QuickResponses in Telephony pre L.
98                QuickResponseUtils.maybeMigrateLegacyQuickResponses(context);
99
100                final SharedPreferences prefs = context.getSharedPreferences(
101                        QuickResponseUtils.SHARED_PREFERENCES_NAME,
102                        Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
103                final Resources res = context.getResources();
104
105                final ArrayList<String> textMessages = new ArrayList<>(
106                        QuickResponseUtils.NUM_CANNED_RESPONSES);
107
108                // Note the default values here must agree with the corresponding
109                // android:defaultValue attributes in respond_via_sms_settings.xml.
110                textMessages.add(0, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1,
111                        res.getString(R.string.respond_via_sms_canned_response_1)));
112                textMessages.add(1, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_2,
113                        res.getString(R.string.respond_via_sms_canned_response_2)));
114                textMessages.add(2, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_3,
115                        res.getString(R.string.respond_via_sms_canned_response_3)));
116                textMessages.add(3, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_4,
117                        res.getString(R.string.respond_via_sms_canned_response_4)));
118
119                Log.d(RespondViaSmsManager.this,
120                        "loadCannedResponses() completed, found responses: %s",
121                        textMessages.toString());
122
123                synchronized (mLock) {
124                    response.onResult(null, textMessages);
125                }
126            }
127        }.start();
128    }
129
130    @Override
131    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
132        if (rejectWithMessage
133                && call.getHandle() != null
134                && !call.can(Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
135            int subId = mCallsManager.getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(
136                    call.getTargetPhoneAccount());
137            rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(),
138                    textMessage, subId);
139        }
140    }
141
142    private void showMessageSentToast(final String phoneNumber, final Context context) {
143        // ...and show a brief confirmation to the user (since
144        // otherwise it's hard to be sure that anything actually
145        // happened.)
146        final Resources res = context.getResources();
147        final String formatString = res.getString(
148                R.string.respond_via_sms_confirmation_format);
149        final String confirmationMsg = String.format(formatString, phoneNumber);
150        int startingPosition = confirmationMsg.indexOf(phoneNumber);
151        int endingPosition = startingPosition + phoneNumber.length();
152
153        Spannable styledConfirmationMsg = new SpannableString(confirmationMsg);
154        PhoneNumberUtils.addTtsSpan(styledConfirmationMsg, startingPosition, endingPosition);
155        Toast.makeText(context, styledConfirmationMsg,
156                Toast.LENGTH_LONG).show();
157
158        // TODO: If the device is locked, this toast won't actually ever
159        // be visible!  (That's because we're about to dismiss the call
160        // screen, which means that the device will return to the
161        // keyguard.  But toasts aren't visible on top of the keyguard.)
162        // Possible fixes:
163        // (1) Is it possible to allow a specific Toast to be visible
164        //     on top of the keyguard?
165        // (2) Artificially delay the dismissCallScreen() call by 3
166        //     seconds to allow the toast to be seen?
167        // (3) Don't use a toast at all; instead use a transient state
168        //     of the InCallScreen (perhaps via the InCallUiState
169        //     progressIndication feature), and have that state be
170        //     visible for 3 seconds before calling dismissCallScreen().
171    }
172
173    /**
174     * Reject the call with the specified message. If message is null this call is ignored.
175     */
176    private void rejectCallWithMessage(Context context, String phoneNumber, String textMessage,
177            int subId) {
178        if (TextUtils.isEmpty(textMessage)) {
179            Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: empty text message. ");
180            return;
181        }
182        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
183            Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: Invalid SubId: " +
184                    subId);
185            return;
186        }
187
188        SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
189        try {
190            smsManager.sendTextMessage(phoneNumber, null, textMessage, null /*sentIntent*/,
191                    null /*deliveryIntent*/);
192
193            SomeArgs args = SomeArgs.obtain();
194            args.arg1 = phoneNumber;
195            args.arg2 = context;
196            mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget();
197        } catch (IllegalArgumentException e) {
198            Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: " +
199                    e.getMessage());
200        }
201    }
202}
203