Merge "HfaActivation should support UI and no-UI modes." into klp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9dc1072..5615b85 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -515,9 +515,10 @@
         </activity>
 
         <activity android:name="HfaActivity"
-                android:excludeFromRecents="true"
+                android:configChanges="orientation|screenSize|keyboardHidden"
                 android:launchMode="singleInstance"
-                android:theme="@style/Empty">
+                android:theme="@style/Empty"
+                android:exported="false">
         </activity>
 
         <receiver android:name="CallerInfoCacheUpdateReceiver">
@@ -535,5 +536,8 @@
             </intent-filter>
         </receiver>
 
+        <!-- service to dump telephony information -->
+        <service android:name="HfaService" android:exported="false"/>
+
     </application>
 </manifest>
diff --git a/src/com/android/phone/HfaActivity.java b/src/com/android/phone/HfaActivity.java
index c1753d0..e3c9345 100644
--- a/src/com/android/phone/HfaActivity.java
+++ b/src/com/android/phone/HfaActivity.java
@@ -20,73 +20,35 @@
 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?
+ * This class operates with Hands Free Activation apps. It comes up during activation
+ * requests that occur outside of setup wizard and so provides its own UI.
+ * It uses {@link HfaLogic} to perform the actual activation and during the process
+ * displays a "performing activation..." dialog.  This will remain up until the user
+ * chooses to skip the activation (still happens in the background) or the activation
+ * is successful.  Upon failure, the dialog also goes away but a subsequent dialog will
+ * ask the user if they would like to try again or cancel.
  */
 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;
+    private HfaLogic mHfaLogic;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -94,7 +56,18 @@
 
         if (VERBOSE) Log.v(TAG, "onCreate");
 
-        startHfaIntentReceiver();
+        mHfaLogic = new HfaLogic(this.getApplicationContext(), new HfaLogic.HfaLogicCallback() {
+            @Override
+            public void onSuccess() {
+                onHfaSuccess();
+            }
+
+            @Override
+            public void onError(String error) {
+                onHfaError(error);
+            }
+        });
+
         startProvisioning();
     }
 
@@ -108,17 +81,11 @@
             mDialog.dismiss();
             mDialog = null;
         }
-
-        if (mReceiver != null) {
-            unregisterReceiver(mReceiver);
-            mReceiver = null;
-        }
     }
 
     private void startProvisioning() {
         buildAndShowDialog();
-
-        sendHfaCommand(ACTION_START);
+        mHfaLogic.start();
     }
 
     private void buildAndShowDialog() {
@@ -132,26 +99,24 @@
                             @Override
                             public void onClick(DialogInterface di, int which) {
                                 if (mCanSkip) {
-                                    sendHfaCommand(ACTION_CANCEL);
-                                    sendResponseToSetupWizard(OTASP_USER_SKIPPED);
+                                    sendFinalResponse(OTASP_USER_SKIPPED);
                                 }
                             }})
                 /*.setOnCancelListener(new DialogInterface.OnCancelListener() {
                     @Override
                     public void onCancel(DialogInterface di) {
-                        sendResponseToSetupWizard(OTASP_USER_SKIPPED);
+                        sendFinalResponse(OTASP_USER_SKIPPED);
                     }})*/
                 .create();
 
+        // Do not allow user to dismiss dialog unless they are clicking "skip"
+        mDialog.setCanceledOnTouchOutside(false);
+        mDialog.setCancelable(false);
+
         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();
 
@@ -162,7 +127,7 @@
                         @Override
                         public void onClick(DialogInterface di, int which) {
                             di.dismiss();
-                            sendResponseToSetupWizard(OTASP_USER_SKIPPED);
+                            sendFinalResponse(OTASP_USER_SKIPPED);
                         }
                     })
             .setNegativeButton(R.string.ota_try_again,
@@ -182,46 +147,10 @@
         // 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();
+        sendFinalResponse(OTASP_SUCCESS);
     }
 
-    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) {
+    private void sendFinalResponse(int responseCode) {
         final PendingIntent otaResponseIntent = getIntent().getParcelableExtra(
                 OtaUtils.EXTRA_OTASP_RESULT_CODE_PENDING_INTENT);
 
@@ -240,39 +169,4 @@
 
         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/HfaLogic.java b/src/com/android/phone/HfaLogic.java
new file mode 100644
index 0000000..7fd37cf
--- /dev/null
+++ b/src/com/android/phone/HfaLogic.java
@@ -0,0 +1,177 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.google.common.base.Preconditions;
+
+/**
+ * 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.
+ * Once the radio is back on we callback the requestor.
+ *
+ * If there is an error, we do not bounce the radio but still callback with a failure.
+ *
+ * TODO(klp): We need system-only permissions for the HFA intents.
+ */
+public class HfaLogic {
+    private static final String TAG = HfaLogic.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 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 BroadcastReceiver mReceiver;
+    private HfaLogicCallback mCallback;
+    private Context mContext;
+
+    public interface HfaLogicCallback {
+        public void onSuccess();
+        public void onError(String errorMsg);
+    }
+
+    public HfaLogic(Context context, HfaLogicCallback callback) {
+        mCallback = Preconditions.checkNotNull(callback);
+        mContext = Preconditions.checkNotNull(context);
+    }
+
+    public void start() {
+        Log.i(TAG, "Start Hfa Provisioning.");
+        startHfaIntentReceiver();
+        startProvisioning();
+    }
+
+    private void startProvisioning() {
+        sendHfaCommand(ACTION_START);
+    }
+
+    private void sendHfaCommand(String action) {
+        if (VERBOSE) Log.v(TAG, "Sending command: " + action);
+        mContext.sendBroadcast(new Intent(action));
+    }
+
+    private void onHfaError(String errorMsg) {
+        stopHfaIntentReceiver();
+        mCallback.onError(errorMsg);
+    }
+
+    private void onHfaSuccess() {
+        stopHfaIntentReceiver();
+        bounceRadio();
+    }
+
+    private void onTotalSuccess() {
+        mCallback.onSuccess();
+    }
+
+    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 on: " + !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);
+
+                onTotalSuccess();
+            }
+        }
+    }
+
+    private 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();
+                }
+            }
+        };
+
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    private void stopHfaIntentReceiver() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+    }
+
+    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/HfaService.java b/src/com/android/phone/HfaService.java
new file mode 100644
index 0000000..a4d13f2
--- /dev/null
+++ b/src/com/android/phone/HfaService.java
@@ -0,0 +1,59 @@
+/*
+ * 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.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Service for performing HfaActivation without any UI.
+ */
+public class HfaService extends Service {
+    private static final String TAG = HfaService.class.getSimpleName();
+
+    @Override
+    public void onCreate() {
+        new HfaLogic(this, new HfaLogic.HfaLogicCallback() {
+            @Override
+            public void onSuccess() {
+                Log.i(TAG, "onSuccess");
+                onComplete();
+            }
+
+            @Override
+            public void onError(String msg) {
+                Log.i(TAG, "onError: " + msg);
+                // We do not respond from this service. On success or failure
+                // we do the same thing...finish.
+                onComplete();
+            }
+        }).start();
+
+        Log.i(TAG, "service started");
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private void onComplete() {
+        stopSelf();
+    }
+}
diff --git a/src/com/android/phone/InCallScreenShowActivation.java b/src/com/android/phone/InCallScreenShowActivation.java
index 8542ae9..fd202db 100644
--- a/src/com/android/phone/InCallScreenShowActivation.java
+++ b/src/com/android/phone/InCallScreenShowActivation.java
@@ -18,9 +18,13 @@
 
 import android.app.Activity;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
@@ -146,20 +150,58 @@
         finish();
     }
 
+    /**
+     * On devices that provide a phone initialization wizard (such as Google Setup Wizard),
+     * the wizard displays it's own activation UI. The Hfa activation started by this class
+     * will show a UI or not depending on the status of the setup wizard. If the setup wizard
+     * is running, do not show a UI, otherwise show our own UI since setup wizard will not.
+     *
+     * The method checks two properties:
+     * 1. Does the device require a setup wizard (ro.setupwizard.mode == (REQUIRED|OPTIONAL))
+     * 2. Is device_provisioned set to non-zero--a property that setup wizard sets at completion.
+     * @return true if wizard is running, false otherwise.
+     */
+    private boolean isWizardRunning(Context context) {
+        Intent intent = new Intent("android.intent.action.DEVICE_INITIALIZATION_WIZARD");
+        ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        boolean provisioned = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        String mode = SystemProperties.get("ro.setupwizard.mode", "REQUIRED");
+        boolean runningSetupWizard = "REQUIRED".equals(mode) || "OPTIONAL".equals(mode);
+        if (DBG) {
+            Log.v(LOG_TAG, "resolvInfo = " + resolveInfo + ", provisioned = " + provisioned
+                    + ", runningSetupWizard = " + runningSetupWizard);
+        }
+        return resolveInfo != null && !provisioned && runningSetupWizard;
+    }
 
     /**
      * Starts the HFA provisioning process by bringing up the HFA Activity.
      */
     private void startHfa() {
-        final Intent intent = new Intent(this, HfaActivity.class);
+        final Intent intent = new Intent();
 
         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);
+        final boolean showUi = !isWizardRunning(this);
+
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        if (otaResponseIntent != null) {
+            intent.putExtra(OtaUtils.EXTRA_OTASP_RESULT_CODE_PENDING_INTENT, otaResponseIntent);
+        }
 
         Log.v(LOG_TAG, "Starting hfa activation activity");
-        startActivity(intent);
+        if (showUi) {
+            intent.setClassName(this, HfaActivity.class.getName());
+            startActivity(intent);
+        } else {
+            intent.setClassName(this, HfaService.class.getName());
+            startService(intent);
+        }
+
+        setResult(RESULT_OK);
     }
 }