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);
}
}