/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.voicemail.impl.fetch;

import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Network;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.os.BuildCompat;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.voicemail.VoicemailComponent;
import com.android.voicemail.impl.VoicemailStatus;
import com.android.voicemail.impl.VvmLog;
import com.android.voicemail.impl.imap.ImapHelper;
import com.android.voicemail.impl.imap.ImapHelper.InitializingException;
import com.android.voicemail.impl.sync.VvmAccountManager;
import com.android.voicemail.impl.sync.VvmNetworkRequestCallback;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/** handles {@link VoicemailContract#ACTION_FETCH_VOICEMAIL} */
@TargetApi(VERSION_CODES.O)
public class FetchVoicemailReceiver extends BroadcastReceiver {

  private static final String TAG = "FetchVoicemailReceiver";

  static final String[] PROJECTION =
      new String[] {
        Voicemails.SOURCE_DATA, // 0
        Voicemails.PHONE_ACCOUNT_ID, // 1
        Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, // 2
      };

  public static final int SOURCE_DATA = 0;
  public static final int PHONE_ACCOUNT_ID = 1;
  public static final int PHONE_ACCOUNT_COMPONENT_NAME = 2;

  // Number of retries
  private static final int NETWORK_RETRY_COUNT = 3;

  private ContentResolver contentResolver;
  private Uri uri;
  private VvmNetworkRequestCallback networkCallback;
  private Context context;
  private String uid;
  private PhoneAccountHandle phoneAccount;
  private int retryCount = NETWORK_RETRY_COUNT;

  @Override
  public void onReceive(final Context context, Intent intent) {
    if (!VoicemailComponent.get(context).getVoicemailClient().isVoicemailModuleEnabled()) {
      return;
    }
    if (VoicemailContract.ACTION_FETCH_VOICEMAIL.equals(intent.getAction())) {
      VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL received");
      this.context = context;
      contentResolver = context.getContentResolver();
      uri = intent.getData();

      if (uri == null) {
        VvmLog.w(TAG, VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data");
        return;
      }

      if (!context
          .getPackageName()
          .equals(uri.getQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE))) {
        // Ignore if the fetch request is for a voicemail not from this package.
        VvmLog.e(TAG, "ACTION_FETCH_VOICEMAIL from foreign pacakge " + context.getPackageName());
        return;
      }

      Cursor cursor = contentResolver.query(uri, PROJECTION, null, null, null);
      if (cursor == null) {
        VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL query returned null");
        return;
      }
      try {
        if (cursor.moveToFirst()) {
          uid = cursor.getString(SOURCE_DATA);
          String accountId = cursor.getString(PHONE_ACCOUNT_ID);
          if (TextUtils.isEmpty(accountId)) {
            TelephonyManager telephonyManager =
                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            accountId = telephonyManager.getSimSerialNumber();

            if (TextUtils.isEmpty(accountId)) {
              VvmLog.e(TAG, "Account null and no default sim found.");
              return;
            }
          }

          phoneAccount =
              new PhoneAccountHandle(
                  ComponentName.unflattenFromString(cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)),
                  cursor.getString(PHONE_ACCOUNT_ID));
          TelephonyManager telephonyManager =
              context
                  .getSystemService(TelephonyManager.class)
                  .createForPhoneAccountHandle(phoneAccount);
          if (telephonyManager == null) {
            // can happen when trying to fetch voicemails from a SIM that is no longer on the
            // device
            VvmLog.e(TAG, "account no longer valid, cannot retrieve message");
            return;
          }
          if (!VvmAccountManager.isAccountActivated(context, phoneAccount)) {
            phoneAccount = getAccountFromMarshmallowAccount(context, phoneAccount);
            if (phoneAccount == null) {
              VvmLog.w(TAG, "Account not registered - cannot retrieve message.");
              return;
            }
            VvmLog.i(TAG, "Fetching voicemail with Marshmallow PhoneAccountHandle");
          }
          VvmLog.i(TAG, "Requesting network to fetch voicemail");
          networkCallback = new fetchVoicemailNetworkRequestCallback(context, phoneAccount);
          networkCallback.requestNetwork();
        }
      } finally {
        cursor.close();
      }
    }
  }

  /**
   * In ag/930496 the format of PhoneAccountHandle has changed between Marshmallow and Nougat. This
   * method attempts to search the account from the old database in registered sources using the old
   * format. There's a chance of M phone account collisions on multi-SIM devices, but visual
   * voicemail is not supported on M multi-SIM.
   */
  @Nullable
  private static PhoneAccountHandle getAccountFromMarshmallowAccount(
      Context context, PhoneAccountHandle oldAccount) {
    if (!BuildCompat.isAtLeastN()) {
      return null;
    }
    for (PhoneAccountHandle handle :
        context.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()) {
      if (getIccSerialNumberFromFullIccSerialNumber(handle.getId()).equals(oldAccount.getId())) {
        return handle;
      }
    }
    return null;
  }

  /**
   * getIccSerialNumber() is used for ID before N, and getFullIccSerialNumber() after.
   * getIccSerialNumber() stops at the first hex char.
   */
  @NonNull
  private static String getIccSerialNumberFromFullIccSerialNumber(@NonNull String id) {
    for (int i = 0; i < id.length(); i++) {
      if (!Character.isDigit(id.charAt(i))) {
        return id.substring(0, i);
      }
    }
    return id;
  }

  private class fetchVoicemailNetworkRequestCallback extends VvmNetworkRequestCallback {

    public fetchVoicemailNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount) {
      super(context, phoneAccount, VoicemailStatus.edit(context, phoneAccount));
    }

    @Override
    public void onAvailable(final Network network) {
      super.onAvailable(network);
      fetchVoicemail(network, getVoicemailStatusEditor());
    }
  }

  private void fetchVoicemail(final Network network, final VoicemailStatus.Editor status) {
    Executor executor = Executors.newCachedThreadPool();
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            if (networkCallback != null) {
                networkCallback.waitForIpv4();
            }
            try {
              while (retryCount > 0) {
                VvmLog.i(TAG, "fetching voicemail, retry count=" + retryCount);
                try (ImapHelper imapHelper =
                    new ImapHelper(context, phoneAccount, network, status)) {
                  boolean success =
                      imapHelper.fetchVoicemailPayload(
                          new VoicemailFetchedCallback(context, uri, phoneAccount), uid);
                  if (!success && retryCount > 0) {
                    VvmLog.i(TAG, "fetch voicemail failed, retrying");
                    retryCount--;
                  } else {
                    return;
                  }
                } catch (InitializingException e) {
                  VvmLog.w(TAG, "Can't retrieve Imap credentials ", e);
                  return;
                }
              }
            } finally {
              if (networkCallback != null) {
                networkCallback.releaseNetwork();
              }
            }
          }
        });
  }
}
