HfaActivation should support UI and no-UI modes.
Split out the HFA logic into it's own class.
Created two new classes that use the logic: HfaService and HfaActivity.
HfaService is run during setupwizard and is completely without UI.
HfaActivity is rung during activations outside of setup wizard and
uses dialogs for the UI, which provice the user the ability to skip the
provisioning if desired (just as before).
We finish the primary activity (InCallScreenShowActivation.java)
immediately when we get the request and continue the new
activity/service as a new task.
bug:10655576
Change-Id: I9b83d4253168829c82c6a1d147ac4eb25a76f39f
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);
}
}