content: /*
   * Copyright (C) 2015 Jacob Klinker
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.mms.service_alt;
  
  import android.app.Activity;
  import android.app.PendingIntent;
  import android.content.Context;
  import android.content.Intent;
  import android.net.ConnectivityManager;
  import android.net.NetworkInfo;
  import android.net.Uri;
  import android.net.wifi.WifiManager;
  import android.os.Build;
  import android.os.Bundle;
  import android.provider.Settings;
  import android.service.carrier.CarrierMessagingService;
  import android.telephony.SmsManager;
  import android.telephony.TelephonyManager;
  import com.klinker.android.logger.Log;
  import com.klinker.android.send_message.Utils;
  
  import com.android.mms.service_alt.exception.ApnException;
  import com.android.mms.service_alt.exception.MmsHttpException;
  
  /**
   * Base class for MMS requests. This has the common logic of sending/downloading MMS.
   */
  public abstract class MmsRequest {
      private static final String TAG = "MmsRequest";
  
      private static final int RETRY_TIMES = 3;
  
      /**
       * Interface for certain functionalities from MmsService
       */
      public static interface RequestManager {
          /**
           * Enqueue an MMS request
           *
           * @param request the request to enqueue
           */
          public void addSimRequest(MmsRequest request);
  
          /*
           * @return Whether to auto persist received MMS
           */
          public boolean getAutoPersistingPref();
  
          /**
           * Read pdu (up to maxSize bytes) from supplied content uri
           * @param contentUri content uri from which to read
           * @param maxSize maximum number of bytes to read
           * @return read pdu (else null in case of error or too big)
           */
          public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize);
  
          /**
           * Write pdu to supplied content uri
           * @param contentUri content uri to which bytes should be written
           * @param pdu pdu bytes to write
           * @return true in case of success (else false)
           */
          public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu);
      }
  
      // The reference to the pending requests manager (i.e. the MmsService)
      protected RequestManager mRequestManager;
      // The SIM id
      protected int mSubId;
      // The creator app
      protected String mCreator;
      // MMS config
      protected MmsConfig.Overridden mMmsConfig;
      // MMS config overrides
      protected Bundle mMmsConfigOverrides;
  
      private boolean mobileDataEnabled;
  
      public MmsRequest(RequestManager requestManager, int subId, String creator,
              Bundle configOverrides) {
          mRequestManager = requestManager;
          mSubId = subId;
          mCreator = creator;
          mMmsConfigOverrides = configOverrides;
          mMmsConfig = null;
      }
  
      public int getSubId() {
          return mSubId;
      }
  
      private boolean ensureMmsConfigLoaded() {
          if (mMmsConfig == null) {
              final MmsConfig config;
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                  // Not yet retrieved from mms config manager. Try getting it.
                  config = MmsConfigManager.getInstance().getMmsConfigBySubId(mSubId);
              } else {
                  config = MmsConfigManager.getInstance().getMmsConfig();
              }
  
              if (config != null) {
                  mMmsConfig = new MmsConfig.Overridden(config, mMmsConfigOverrides);
              }
          }
          return mMmsConfig != null;
      }
  
      private static boolean inAirplaneMode(final Context context) {
          return Settings.System.getInt(context.getContentResolver(),
                  Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
      }
  
      private static boolean isMobileDataEnabled(final Context context, final int subId) {
          final TelephonyManager telephonyManager =
                  (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
          return Utils.isDataEnabled(telephonyManager, subId);
      }
  
      private static boolean isDataNetworkAvailable(final Context context, final int subId) {
          return !inAirplaneMode(context) && isMobileDataEnabled(context, subId);
      }
  
      /**
       * Execute the request
       *
       * @param context The context
       * @param networkManager The network manager to use
       */
      public void execute(Context context, MmsNetworkManager networkManager) {
          int result = SmsManager.MMS_ERROR_UNSPECIFIED;
          int httpStatusCode = 0;
          byte[] response = null;
  
          WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
          boolean isWifiEnabled = wifi.isWifiEnabled();
  
          if (!useWifi(context)) {
              wifi.setWifiEnabled(false);
          }
  
          mobileDataEnabled = Utils.isMobileDataEnabled(context);
          Log.v(TAG, "mobile data enabled: " + mobileDataEnabled);
  
          if (!mobileDataEnabled && !useWifi(context)) {
              Log.v(TAG, "mobile data not enabled, so forcing it to enable");
              Utils.setMobileDataEnabled(context, true);
          }
  
          if (!ensureMmsConfigLoaded()) { // Check mms config
              Log.e(TAG, "MmsRequest: mms config is not loaded yet");
              result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR;
          } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
              Log.e(TAG, "MmsRequest: failed to prepare for request");
              result = SmsManager.MMS_ERROR_IO_ERROR;
          } else if (!isDataNetworkAvailable(context, mSubId)) {
              Log.e(TAG, "MmsRequest: in airplane mode or mobile data disabled");
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                  result = SmsManager.MMS_ERROR_NO_DATA_NETWORK;
              } else {
                  result = 8;
              }
          } else { // Execute
              long retryDelaySecs = 2;
              // Try multiple times of MMS HTTP request
              for (int i = 0; i < RETRY_TIMES; i++) {
                  try {
                      try {
                          networkManager.acquireNetwork();
                      } catch (Exception e) {
                          Log.e(TAG, "error acquiring network", e);
                      }
  
                      final String apnName = networkManager.getApnName();
                      try {
                          ApnSettings apn = null;
                          try {
                              apn = ApnSettings.load(context, apnName, mSubId);
                          } catch (ApnException e) {
                              // If no APN could be found, fall back to trying without the APN name
                              if (apnName == null) {
                                  // If the APN name was already null then don't need to retry
                                  throw (e);
                              }
                              Log.i(TAG, "MmsRequest: No match with APN name:"
                                      + apnName + ", try with no name");
                              apn = ApnSettings.load(context, null, mSubId);
                          }
                          Log.i(TAG, "MmsRequest: using " + apn.toString());
                          response = doHttp(context, networkManager, apn);
                          result = Activity.RESULT_OK;
                          // Success
                          break;
                      } finally {
                          networkManager.releaseNetwork();
                      }
                  } catch (ApnException e) {
                      Log.e(TAG, "MmsRequest: APN failure", e);
                      result = SmsManager.MMS_ERROR_INVALID_APN;
                      break;
  //                    } catch (MmsNetworkException e) {
  //                        Log.e(TAG, "MmsRequest: MMS network acquiring failure", e);
  //                        result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
  //                        // Retry
                  } catch (MmsHttpException e) {
                      Log.e(TAG, "MmsRequest: HTTP or network I/O failure", e);
                      result = SmsManager.MMS_ERROR_HTTP_FAILURE;
                      httpStatusCode = e.getStatusCode();
                      // Retry
                  } catch (Exception e) {
                      Log.e(TAG, "MmsRequest: unexpected failure", e);
                      result = SmsManager.MMS_ERROR_UNSPECIFIED;
                      break;
                  }
                  try {
                      Thread.sleep(retryDelaySecs * 1000, 0/*nano*/);
                  } catch (InterruptedException e) {
                  }
                  retryDelaySecs <<= 1;
              }
          }
  
          if (!mobileDataEnabled) {
              Log.v(TAG, "setting mobile data back to disabled");
              Utils.setMobileDataEnabled(context, false);
          }
  
          if (!useWifi(context)) {
              wifi.setWifiEnabled(isWifiEnabled);
          }
  
          processResult(context, result, response, httpStatusCode);
      }
  
      /**
       * Process the result of the completed request, including updating the message status
       * in database and sending back the result via pending intents.
       *  @param context The context
       * @param result The result code of execution
       * @param response The response body
       * @param httpStatusCode The optional http status code in case of http failure
       */
      public void processResult(Context context, int result, byte[] response, int httpStatusCode) {
          final Uri messageUri = persistIfRequired(context, result, response);
  
          // Return MMS HTTP request result via PendingIntent
          final PendingIntent pendingIntent = getPendingIntent();
          if (pendingIntent != null) {
              boolean succeeded = true;
              // Extra information to send back with the pending intent
              Intent fillIn = new Intent();
              if (response != null) {
                  succeeded = transferResponse(fillIn, response);
              }
              if (messageUri != null) {
                  fillIn.putExtra("uri", messageUri.toString());
              }
              if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) {
                  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                      fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode);
                  } else {
                      fillIn.putExtra("android.telephony.extra.MMS_HTTP_STATUS", httpStatusCode);
                  }
              }
              try {
                  if (!succeeded) {
                      result = SmsManager.MMS_ERROR_IO_ERROR;
                  }
                  pendingIntent.send(context, result, fillIn);
              } catch (PendingIntent.CanceledException e) {
                  Log.e(TAG, "MmsRequest: sending pending intent canceled", e);
              }
          }
  
          revokeUriPermission(context);
      }
  
      /**
       * are we set up to use wifi? if so, send mms over it.
       */
      public static boolean useWifi(Context context) {
          if (Utils.isMmsOverWifiEnabled(context)) {
              ConnectivityManager mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
              if (mConnMgr != null) {
                  NetworkInfo niWF = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
                  if ((niWF != null) && (niWF.isConnected())) {
                      return true;
                  }
              }
          }
          return false;
      }
  
      /**
       * Returns true if sending / downloading using the carrier app has failed and completes the
       * action using platform API's, otherwise false.
       */
      protected boolean maybeFallbackToRegularDelivery(int carrierMessagingAppResult) {
          if (carrierMessagingAppResult
                  == CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK
                  || carrierMessagingAppResult
                          == CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK) {
              Log.d(TAG, "Sending/downloading MMS by IP failed.");
              mRequestManager.addSimRequest(MmsRequest.this);
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * Converts from {@code carrierMessagingAppResult} to a platform result code.
       */
      protected static int toSmsManagerResult(int carrierMessagingAppResult) {
          switch (carrierMessagingAppResult) {
              case CarrierMessagingService.SEND_STATUS_OK:
                  return Activity.RESULT_OK;
              case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
                  return SmsManager.MMS_ERROR_RETRY;
              default:
                  return SmsManager.MMS_ERROR_UNSPECIFIED;
          }
      }
  
      /**
       * Making the HTTP request to MMSC
       *
       * @param context The context
       * @param netMgr The current {@link MmsNetworkManager}
       * @param apn The APN setting
       * @return The HTTP response data
       * @throws MmsHttpException If any network error happens
       */
      protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
              throws MmsHttpException;
  
      /**
       * @return The PendingIntent associate with the MMS sending invocation
       */
      protected abstract PendingIntent getPendingIntent();
  
      /**
       * @return The queue should be used by this request, 0 is sending and 1 is downloading
       */
      protected abstract int getQueueType();
  
      /**
       * Persist message into telephony if required (i.e. when auto-persisting is on or
       * the calling app is non-default sms app for sending)
       *
       * @param context The context
       * @param result The result code of execution
       * @param response The response body
       * @return The persisted URI of the message or null if we don't persist or fail
       */
      protected abstract Uri persistIfRequired(Context context, int result, byte[] response);
  
      /**
       * Prepare to make the HTTP request - will download message for sending
       * @return true if preparation succeeds (and request can proceed) else false
       */
      protected abstract boolean prepareForHttpRequest();
  
      /**
       * Transfer the received response to the caller
       *
       * @param fillIn the intent that will be returned to the caller
       * @param response the pdu to transfer
       * @return true if response transfer succeeds else false
       */
      protected abstract boolean transferResponse(Intent fillIn, byte[] response);
  
      /**
       * Revoke the content URI permission granted by the MMS app to the phone package.
       *
       * @param context The context
       */
      protected abstract void revokeUriPermission(Context context);
  
  }
  