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 ACTION_REQUEST_SENT_INTENT = 57 "com.android.voicemailomtp.sms.REQUEST_SENT"; 58 private static final int ACTION_REQUEST_SENT_REQUEST_CODE = 0; 59 60 private CompletableFuture<Bundle> future = new CompletableFuture<>(); 61 62 private final Context context; 63 private final PhoneAccountHandle phoneAccountHandle; 64 StatusSmsFetcher(Context context, PhoneAccountHandle phoneAccountHandle)65 public StatusSmsFetcher(Context context, PhoneAccountHandle phoneAccountHandle) { 66 this.context = context; 67 this.phoneAccountHandle = phoneAccountHandle; 68 IntentFilter filter = new IntentFilter(ACTION_REQUEST_SENT_INTENT); 69 filter.addAction(OmtpService.ACTION_SMS_RECEIVED); 70 context.registerReceiver(this, filter); 71 } 72 73 @Override close()74 public void close() throws IOException { 75 context.unregisterReceiver(this); 76 } 77 78 @WorkerThread 79 @Nullable get()80 public Bundle get() 81 throws InterruptedException, ExecutionException, TimeoutException, CancellationException { 82 Assert.isNotMainThread(); 83 return future.get(STATUS_SMS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 84 } 85 getSentIntent()86 public PendingIntent getSentIntent() { 87 Intent intent = new Intent(ACTION_REQUEST_SENT_INTENT); 88 intent.setPackage(context.getPackageName()); 89 // Because the receiver is registered dynamically, implicit intent must be used. 90 // There should only be a single status SMS request at a time. 91 return PendingIntent.getBroadcast( 92 context, ACTION_REQUEST_SENT_REQUEST_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT); 93 } 94 95 @Override 96 @MainThread onReceive(Context context, Intent intent)97 public void onReceive(Context context, Intent intent) { 98 Assert.isMainThread(); 99 if (ACTION_REQUEST_SENT_INTENT.equals(intent.getAction())) { 100 int resultCode = getResultCode(); 101 102 if (resultCode == Activity.RESULT_OK) { 103 VvmLog.d(TAG, "Request SMS successfully sent"); 104 return; 105 } 106 107 VvmLog.e(TAG, "Request SMS send failed: " + sentSmsResultToString(resultCode)); 108 future.cancel(true); 109 return; 110 } 111 112 VisualVoicemailSms sms = intent.getExtras().getParcelable(OmtpService.EXTRA_VOICEMAIL_SMS); 113 114 if (!phoneAccountHandle.equals(sms.getPhoneAccountHandle())) { 115 return; 116 } 117 String eventType = sms.getPrefix(); 118 119 if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) { 120 future.complete(sms.getFields()); 121 return; 122 } 123 124 if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) { 125 return; 126 } 127 128 VvmLog.i( 129 TAG, 130 "VVM SMS with event " + eventType + " received, attempting to translate to STATUS SMS"); 131 OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, phoneAccountHandle); 132 VisualVoicemailProtocol protocol = helper.getProtocol(); 133 if (protocol == null) { 134 return; 135 } 136 Bundle translatedBundle = protocol.translateStatusSmsBundle(helper, eventType, sms.getFields()); 137 138 if (translatedBundle != null) { 139 VvmLog.i(TAG, "Translated to STATUS SMS"); 140 future.complete(translatedBundle); 141 } 142 } 143 sentSmsResultToString(int resultCode)144 private static String sentSmsResultToString(int resultCode) { 145 switch (resultCode) { 146 case Activity.RESULT_OK: 147 return "OK"; 148 case SmsManager.RESULT_ERROR_GENERIC_FAILURE: 149 return "RESULT_ERROR_GENERIC_FAILURE"; 150 case SmsManager.RESULT_ERROR_NO_SERVICE: 151 return "RESULT_ERROR_GENERIC_FAILURE"; 152 case SmsManager.RESULT_ERROR_NULL_PDU: 153 return "RESULT_ERROR_GENERIC_FAILURE"; 154 case SmsManager.RESULT_ERROR_RADIO_OFF: 155 return "RESULT_ERROR_GENERIC_FAILURE"; 156 default: 157 return "UNKNOWN CODE: " + resultCode; 158 } 159 } 160 } 161