HFA support in services/Telephony for provisioning CDMA devices.

Adds a new activity that shows some ui allowing the user to see progress
and cancel the action.

At the end of the action (through error or completion) we return to the
setup wizard.

Change-Id: Idfe58d7b93463b59fc0de3eebd03517a36177d22
diff --git a/src/com/android/phone/HfaActivity.java b/src/com/android/phone/HfaActivity.java
new file mode 100644
index 0000000..3bc047d
--- /dev/null
+++ b/src/com/android/phone/HfaActivity.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2013 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.phone;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+
+/**
+ * Starts and displays status for Hands Free Activation (HFA).
+ *
+ * This class operates with Hands Free Activation apps.
+ * It starts by broadcasting the intent com.android.action.START_HFA.
+ * An HFA app will pick that up and start the HFA process.
+ * If it fails it return ERROR_HFA Intent and upon success returns COMPLETE_HFA.
+ *
+ * If successful, we bounce the radio so that the service picks up the new number. This is also
+ * necessary for the setup wizard to pick up the successful activation so that it can continue
+ * past the welcome screen. Once the radio is back on we send back the pendingIntent to setup
+ * wizard and destroy the activity.
+ *
+ * If there is an error, we do not bounce the radio but still send the pending intent back to
+ * the wizard (with a failure code).
+ *
+ * The user has an option to skip activation. If that happens, we go back to the setup
+ * wizard.
+ *
+ * TODO(klp): We need system-only permissions for the HFA intents.
+ * TODO(klp): Should be full screen activity instead of dialogs.
+ * TODO(klp): Currently display the error code instead of the error string resource.
+ * TODO(klp): Need to check the system to ensure someone is listening for the intent
+ *            before we send it.  Should there be a timeout?  5 minutes?
+ */
+public class HfaActivity extends Activity {
+    private static final String TAG = HfaActivity.class.getSimpleName();
+
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final String ACTION_START = "com.android.action.START_HFA";
+    private static final String ACTION_ERROR = "com.android.action.ERROR_HFA";
+    private static final String ACTION_CANCEL = "com.android.action.CANCEL_HFA";
+    private static final String ACTION_COMPLETE = "com.android.action.COMPLETE_HFA";
+
+    private static final int SERVICE_STATE_CHANGED = 1;
+
+    public static final int OTASP_UNKNOWN = 0;
+    public static final int OTASP_USER_SKIPPED = 1;
+    public static final int OTASP_SUCCESS = 2;
+    public static final int OTASP_FAILURE = 3;
+
+    public static final int NOT_WAITING = 0;
+    public static final int WAITING_FOR_RADIO_OFF = 1;
+    public static final int WAITING_FOR_RADIO_ON = 2;
+
+    private int mPhoneMonitorState = NOT_WAITING;
+    private boolean mCanSkip;
+    private AlertDialog mDialog;
+    private BroadcastReceiver mReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (VERBOSE) Log.v(TAG, "onCreate");
+
+        startHfaIntentReceiver();
+        startProvisioning();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (VERBOSE) Log.v(TAG, "onDestroy");
+
+        if (mDialog != null && mDialog.isShowing()) {
+            mDialog.dismiss();
+            mDialog = null;
+        }
+
+        if (mReceiver != null) {
+            unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+    }
+
+    private void startProvisioning() {
+        buildAndShowDialog();
+
+        sendHfaCommand(ACTION_START);
+    }
+
+    private void buildAndShowDialog() {
+        mCanSkip = true;
+
+        mDialog = new AlertDialog.Builder(this)
+                .setTitle(R.string.ota_hfa_activation_title)
+                .setMessage(R.string.ota_hfa_activation_dialog_message)
+                .setPositiveButton(R.string.ota_skip_activation_dialog_skip_label,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface di, int which) {
+                                if (mCanSkip) {
+                                    sendHfaCommand(ACTION_CANCEL);
+                                    sendResponseToSetupWizard(OTASP_USER_SKIPPED);
+                                }
+                            }})
+                /*.setOnCancelListener(new DialogInterface.OnCancelListener() {
+                    @Override
+                    public void onCancel(DialogInterface di) {
+                        sendResponseToSetupWizard(OTASP_USER_SKIPPED);
+                    }})*/
+                .create();
+
+        if (VERBOSE) Log.v(TAG, "showing dialog");
+        mDialog.show();
+    }
+
+    private void sendHfaCommand(String action) {
+        if (VERBOSE) Log.v(TAG, "Sending command: " + action);
+        sendBroadcast(new Intent(action));
+    }
+
+    private void onHfaError(String errorMsg) {
+        mDialog.dismiss();
+
+        AlertDialog errorDialog = new AlertDialog.Builder(this)
+            .setMessage(errorMsg)
+            .setPositiveButton(R.string.ota_skip_activation_dialog_skip_label,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface di, int which) {
+                            di.dismiss();
+                            sendResponseToSetupWizard(OTASP_USER_SKIPPED);
+                        }
+                    })
+            .setNegativeButton(R.string.ota_try_again,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface di, int which) {
+                            di.dismiss();
+                            startProvisioning();
+                        }
+                    })
+            .create();
+
+        errorDialog.show();
+    }
+
+    private void onHfaSuccess() {
+        // User can no longer skip after success.
+        mCanSkip = false;
+
+        // We need to restart the modem upon successful activation
+        // so that it can acquire a number and ensure setupwizard will
+        // know about this success through phone state changes.
+
+        bounceRadio();
+    }
+
+    private void bounceRadio() {
+        final Phone phone = PhoneGlobals.getInstance().getPhone();
+        phone.registerForServiceStateChanged(mHandler, SERVICE_STATE_CHANGED, null);
+
+        mPhoneMonitorState = WAITING_FOR_RADIO_OFF;
+        phone.setRadioPower(false);
+        onServiceStateChange(phone.getServiceState());
+    }
+
+    private void onServiceStateChange(ServiceState state) {
+        final boolean radioIsOff = state.getVoiceRegState() == ServiceState.STATE_POWER_OFF;
+        final Phone phone = PhoneGlobals.getInstance().getPhone();
+
+        if (VERBOSE) Log.v(TAG, "Radio is off: " + radioIsOff);
+
+        if (mPhoneMonitorState == WAITING_FOR_RADIO_OFF) {
+            if (radioIsOff) {
+                mPhoneMonitorState = WAITING_FOR_RADIO_ON;
+                phone.setRadioPower(true);
+            }
+        } else if (mPhoneMonitorState == WAITING_FOR_RADIO_ON) {
+            if (!radioIsOff) {
+                mPhoneMonitorState = NOT_WAITING;
+                phone.unregisterForServiceStateChanged(mHandler);
+
+                // We have successfully bounced the radio.
+                // Time to go back to the setup wizard.
+                sendResponseToSetupWizard(OTASP_SUCCESS);
+            }
+        }
+    }
+
+    private void sendResponseToSetupWizard(int responseCode) {
+        final PendingIntent otaResponseIntent = getIntent().getParcelableExtra(
+                OtaUtils.EXTRA_OTASP_RESULT_CODE_PENDING_INTENT);
+
+        final Intent extraStuff = new Intent();
+        extraStuff.putExtra(OtaUtils.EXTRA_OTASP_RESULT_CODE, responseCode);
+
+        try {
+            if (VERBOSE) Log.v(TAG, "Sending OTASP confirmation with result code: " + responseCode);
+            otaResponseIntent.send(this, 0 /* resultCode (not used) */, extraStuff);
+        } catch (CanceledException e) {
+            Log.e(TAG, "Pending Intent canceled");
+        }
+
+        finish();
+    }
+
+    public void startHfaIntentReceiver() {
+        final IntentFilter filter = new IntentFilter(ACTION_COMPLETE);
+        filter.addAction(ACTION_ERROR);
+
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (action.equals(ACTION_ERROR)) {
+                    onHfaError(intent.getStringExtra("errorCode"));
+                } else if (action.equals(ACTION_COMPLETE)) {
+                    if (VERBOSE) Log.v(TAG, "Hfa Successful");
+                    onHfaSuccess();
+                }
+            }
+        };
+
+        registerReceiver(mReceiver, filter);
+    }
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SERVICE_STATE_CHANGED:
+                    ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
+                    onServiceStateChange(state);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+}
diff --git a/src/com/android/phone/InCallScreenShowActivation.java b/src/com/android/phone/InCallScreenShowActivation.java
index 243b437..15abb11 100644
--- a/src/com/android/phone/InCallScreenShowActivation.java
+++ b/src/com/android/phone/InCallScreenShowActivation.java
@@ -67,6 +67,14 @@
 
         if (intent.getAction().equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
 
+            boolean usesHfa = getResources().getBoolean(R.bool.config_use_hfa_for_provisioning);
+            if (usesHfa) {
+                Log.d(LOG_TAG, "Starting Hfa from ACTION_PERFORM_CDMA_PROVISIONING");
+                startHfa();
+                finish();
+                return;
+            }
+
             // On voice-capable devices, we perform CDMA provisioning in
             // "interactive" mode by directly launching the InCallScreen.
             // boolean interactiveMode = PhoneGlobals.sVoiceCapable;
@@ -137,4 +145,21 @@
 
         finish();
     }
+
+
+    /**
+     * Starts the HFA provisioning process by bringing up the HFA Activity.
+     */
+    private void startHfa() {
+        final Intent intent = new Intent(this, HfaActivity.class);
+
+        final PendingIntent otaResponseIntent = getIntent().getParcelableExtra(
+                OtaUtils.EXTRA_OTASP_RESULT_CODE_PENDING_INTENT);
+
+        intent.putExtra(OtaUtils.EXTRA_OTASP_RESULT_CODE_PENDING_INTENT, otaResponseIntent);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+        Log.v(LOG_TAG, "Starting hfa activation activity");
+        startActivity(intent);
+    }
 }
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index db06c30..45ffd33 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -1588,4 +1588,5 @@
             mBluetoothPhone = null;
         }
     };
+
 }