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 17package com.android.voicemail.impl.sms; 18 19import android.annotation.TargetApi; 20import android.app.Activity; 21import android.app.PendingIntent; 22import android.content.BroadcastReceiver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.os.Build.VERSION_CODES; 27import android.os.Bundle; 28import android.support.annotation.MainThread; 29import android.support.annotation.Nullable; 30import android.support.annotation.WorkerThread; 31import android.telecom.PhoneAccountHandle; 32import android.telephony.SmsManager; 33import android.telephony.VisualVoicemailSms; 34import com.android.voicemail.impl.Assert; 35import com.android.voicemail.impl.OmtpConstants; 36import com.android.voicemail.impl.OmtpService; 37import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper; 38import com.android.voicemail.impl.VvmLog; 39import com.android.voicemail.impl.protocol.VisualVoicemailProtocol; 40import java.io.Closeable; 41import java.io.IOException; 42import java.util.concurrent.CancellationException; 43import java.util.concurrent.CompletableFuture; 44import java.util.concurrent.ExecutionException; 45import java.util.concurrent.TimeUnit; 46import 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) 51public 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