1 /*
2  * Copyright (C) 2015 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 
17 package com.android.voicemail.impl.sms;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.app.PendingIntent;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Build.VERSION_CODES;
27 import android.os.Bundle;
28 import android.support.annotation.MainThread;
29 import android.support.annotation.Nullable;
30 import android.support.annotation.WorkerThread;
31 import android.telecom.PhoneAccountHandle;
32 import android.telephony.SmsManager;
33 import android.telephony.VisualVoicemailSms;
34 import com.android.voicemail.impl.Assert;
35 import com.android.voicemail.impl.OmtpConstants;
36 import com.android.voicemail.impl.OmtpService;
37 import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper;
38 import com.android.voicemail.impl.VvmLog;
39 import com.android.voicemail.impl.protocol.VisualVoicemailProtocol;
40 import java.io.Closeable;
41 import java.io.IOException;
42 import java.util.concurrent.CancellationException;
43 import java.util.concurrent.CompletableFuture;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 
48 /** Intercepts a incoming STATUS SMS with a blocking call. */
49 @TargetApi(VERSION_CODES.O)
50 public class StatusSmsFetcher extends BroadcastReceiver implements Closeable {
51 
52   private static final String TAG = "VvmStatusSmsFetcher";
53 
54   private static final long STATUS_SMS_TIMEOUT_MILLIS = 60_000;
55 
56   private static final String PERMISSION_DIALER_ORIGIN =
57       "com.android.dialer.permission.DIALER_ORIGIN";
58 
59   private static final String ACTION_REQUEST_SENT_INTENT =
60       "com.android.voicemailomtp.sms.REQUEST_SENT";
61 
62   private static final int ACTION_REQUEST_SENT_REQUEST_CODE = 0;
63 
64   private CompletableFuture<Bundle> future = new CompletableFuture<>();
65 
66   private final Context context;
67   private final PhoneAccountHandle phoneAccountHandle;
68 
StatusSmsFetcher(Context context, PhoneAccountHandle phoneAccountHandle)69   public StatusSmsFetcher(Context context, PhoneAccountHandle phoneAccountHandle) {
70     this.context = context;
71     this.phoneAccountHandle = phoneAccountHandle;
72     IntentFilter filter = new IntentFilter(ACTION_REQUEST_SENT_INTENT);
73     filter.addAction(OmtpService.ACTION_SMS_RECEIVED);
74     context.registerReceiver(this, filter, PERMISSION_DIALER_ORIGIN, /* scheduler= */ null);
75   }
76 
77   @Override
close()78   public void close() throws IOException {
79     context.unregisterReceiver(this);
80   }
81 
82   @WorkerThread
83   @Nullable
get()84   public Bundle get()
85       throws InterruptedException, ExecutionException, TimeoutException, CancellationException {
86     Assert.isNotMainThread();
87     return future.get(STATUS_SMS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
88   }
89 
getSentIntent()90   public PendingIntent getSentIntent() {
91     Intent intent = new Intent(ACTION_REQUEST_SENT_INTENT);
92     intent.setPackage(context.getPackageName());
93     // Because the receiver is registered dynamically, implicit intent must be used.
94     // There should only be a single status SMS request at a time.
95     return PendingIntent.getBroadcast(
96         context,
97         ACTION_REQUEST_SENT_REQUEST_CODE,
98         intent,
99         PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
100   }
101 
102   @Override
103   @MainThread
onReceive(Context context, Intent intent)104   public void onReceive(Context context, Intent intent) {
105     Assert.isMainThread();
106     if (ACTION_REQUEST_SENT_INTENT.equals(intent.getAction())) {
107       int resultCode = getResultCode();
108 
109       if (resultCode == Activity.RESULT_OK) {
110         VvmLog.d(TAG, "Request SMS successfully sent");
111         return;
112       }
113 
114       VvmLog.e(TAG, "Request SMS send failed: " + sentSmsResultToString(resultCode));
115       future.cancel(true);
116       return;
117     }
118 
119     VisualVoicemailSms sms = intent.getExtras().getParcelable(OmtpService.EXTRA_VOICEMAIL_SMS);
120 
121     if (!phoneAccountHandle.equals(sms.getPhoneAccountHandle())) {
122       return;
123     }
124     String eventType = sms.getPrefix();
125 
126     if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
127       future.complete(sms.getFields());
128       return;
129     }
130 
131     if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
132       return;
133     }
134 
135     VvmLog.i(
136         TAG,
137         "VVM SMS with event " + eventType + " received, attempting to translate to STATUS SMS");
138     OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, phoneAccountHandle);
139     VisualVoicemailProtocol protocol = helper.getProtocol();
140     if (protocol == null) {
141       return;
142     }
143     Bundle translatedBundle = protocol.translateStatusSmsBundle(helper, eventType, sms.getFields());
144 
145     if (translatedBundle != null) {
146       VvmLog.i(TAG, "Translated to STATUS SMS");
147       future.complete(translatedBundle);
148     }
149   }
150 
sentSmsResultToString(int resultCode)151   private static String sentSmsResultToString(int resultCode) {
152     switch (resultCode) {
153       case Activity.RESULT_OK:
154         return "OK";
155       case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
156         return "RESULT_ERROR_GENERIC_FAILURE";
157       case SmsManager.RESULT_ERROR_NO_SERVICE:
158         return "RESULT_ERROR_GENERIC_FAILURE";
159       case SmsManager.RESULT_ERROR_NULL_PDU:
160         return "RESULT_ERROR_GENERIC_FAILURE";
161       case SmsManager.RESULT_ERROR_RADIO_OFF:
162         return "RESULT_ERROR_GENERIC_FAILURE";
163       default:
164         return "UNKNOWN CODE: " + resultCode;
165     }
166   }
167 }
168