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