Home | History | Annotate | Download | only in sms
      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