Merge "Tweak change voicemail PIN UI" into nyc-mr1-dev
diff --git a/src/com/android/phone/vvm/omtp/ActivationTask.java b/src/com/android/phone/vvm/omtp/ActivationTask.java
index eb43073..e1fea4d 100644
--- a/src/com/android/phone/vvm/omtp/ActivationTask.java
+++ b/src/com/android/phone/vvm/omtp/ActivationTask.java
@@ -43,6 +43,7 @@
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@@ -101,9 +102,7 @@
// Check for signal before activating. The event often happen while boot and the
// network is not connected yet. Launching activation will likely to cause the SMS
// sending to fail and waste unnecessary time waiting for time out.
- if (context.getSystemService(TelephonyManager.class)
- .getServiceStateForSubscriber(subId).getState()
- != ServiceState.STATE_IN_SERVICE) {
+ if (!hasSignal(context, subId)) {
VvmLog.i(TAG, "Activation requested while not in service, rejecting");
}
@@ -139,6 +138,12 @@
return;
}
+ if (!hasSignal(getContext(), subId)) {
+ VvmLog.i(TAG, "Service lost during activation, aborting");
+ // Don't retry, a new activation will be started after the signal returned.
+ return;
+ }
+
OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(getContext(), subId);
if (!helper.isValid()) {
VvmLog.i(TAG, "VVM not supported on subId " + subId);
@@ -190,7 +195,7 @@
data = mMessageData;
} else {
try (StatusSmsFetcher fetcher = new StatusSmsFetcher(getContext(), subId)) {
- protocol.startActivation(helper);
+ protocol.startActivation(helper, fetcher.getSentIntent());
// Both the fetcher and OmtpMessageReceiver will be triggered, but
// OmtpMessageReceiver will just route the SMS back to ActivationTask, which will be
// rejected because the task is still running.
@@ -201,6 +206,10 @@
helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
fail();
return;
+ } catch (CancellationException e) {
+ VvmLog.e(TAG, "Unable to send status request SMS");
+ fail();
+ return;
} catch (InterruptedException | ExecutionException | IOException e) {
VvmLog.e(TAG, "can't get future STATUS SMS", e);
fail();
@@ -257,6 +266,11 @@
}
}
+ private static boolean hasSignal(Context context, int subId) {
+ return context.getSystemService(TelephonyManager.class)
+ .getServiceStateForSubscriber(subId).getState() == ServiceState.STATE_IN_SERVICE;
+ }
+
private static void queueActivationAfterProvisioned(Context context, int subId) {
if (sDeviceProvisionedObserver == null) {
sDeviceProvisionedObserver = new DeviceProvisionedObserver(context);
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 147eaf1..0b321b5 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -16,6 +16,7 @@
package com.android.phone.vvm.omtp;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
@@ -364,9 +365,9 @@
}
}
- public void requestStatus() {
+ public void requestStatus(@Nullable PendingIntent sentIntent) {
if (mProtocol != null) {
- mProtocol.requestStatus(this);
+ mProtocol.requestStatus(this, sentIntent);
}
}
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
index e0b6359..6e6d6ef 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -17,6 +17,7 @@
package com.android.phone.vvm.omtp.protocol;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.os.Bundle;
import android.telecom.PhoneAccountHandle;
@@ -34,10 +35,10 @@
/**
* Activation should cause the carrier to respond with a STATUS SMS.
*/
- public void startActivation(OmtpVvmCarrierConfigHelper config) {
+ public void startActivation(OmtpVvmCarrierConfigHelper config, PendingIntent sentIntent) {
OmtpMessageSender messageSender = ProtocolHelper.getMessageSender(this, config);
if (messageSender != null) {
- messageSender.requestVvmActivation(null);
+ messageSender.requestVvmActivation(sentIntent);
}
}
@@ -58,10 +59,11 @@
// Do nothing
}
- public void requestStatus(OmtpVvmCarrierConfigHelper config) {
+ public void requestStatus(OmtpVvmCarrierConfigHelper config,
+ @Nullable PendingIntent sentIntent) {
OmtpMessageSender messageSender = ProtocolHelper.getMessageSender(this, config);
if (messageSender != null) {
- messageSender.requestVvmStatus(null);
+ messageSender.requestVvmStatus(sentIntent);
}
}
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
index 39d2b34..72044e2 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -17,6 +17,7 @@
package com.android.phone.vvm.omtp.protocol;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.net.Network;
import android.os.Bundle;
@@ -82,12 +83,13 @@
private static final int DEFAULT_PIN_LENGTH = 6;
@Override
- public void startActivation(OmtpVvmCarrierConfigHelper config) {
+ public void startActivation(OmtpVvmCarrierConfigHelper config,
+ @Nullable PendingIntent sentIntent) {
// VVM3 does not support activation SMS.
// Send a status request which will start the provisioning process if the user is not
// provisioned.
VvmLog.i(TAG, "Activating");
- config.requestStatus();
+ config.requestStatus(sentIntent);
}
@Override
@@ -215,7 +217,7 @@
helper.closeNewUserTutorial();
VvmLog.i(TAG, "new user: NUT closed");
- config.requestStatus();
+ config.requestStatus(null);
}
} catch (InitializingException | MessagingException | IOException e) {
config.handleEvent(status, OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
diff --git a/src/com/android/phone/vvm/omtp/scheduling/TaskSchedulerService.java b/src/com/android/phone/vvm/omtp/scheduling/TaskSchedulerService.java
index 1e099e8..3d6fcdb 100644
--- a/src/com/android/phone/vvm/omtp/scheduling/TaskSchedulerService.java
+++ b/src/com/android/phone/vvm/omtp/scheduling/TaskSchedulerService.java
@@ -19,6 +19,8 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -30,6 +32,7 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.Assert;
import com.android.phone.NeededForTesting;
@@ -45,9 +48,25 @@
*/
public class TaskSchedulerService extends Service {
- private static final String TAG = "TaskSchedulerService";
+ private static final String TAG = "VvmTaskScheduler";
+
+ private static final String ACTION_WAKEUP = "action_wakeup";
private static final int READY_TOLERANCE_MILLISECONDS = 100;
+
+ /**
+ * Threshold to determine whether to do a short or long sleep when a task is scheduled in the
+ * future.
+ *
+ * <p>A short sleep will continue to held the wake lock and use {@link
+ * Handler#postDelayed(Runnable, long)} to wait for the next task.
+ *
+ * <p>A long sleep will release the wake lock and set a {@link AlarmManager} alarm. The alarm is
+ * exact and will wake up the device. Note: as this service is run in the telephony process it
+ * does not seem to be restricted by doze or sleep, it will fire exactly at the moment. The
+ * unbundled version should take doze into account.
+ */
+ private static final int SHORT_SLEEP_THRESHOLD_MILLISECONDS = 60_000;
/**
* When there are no more tasks to be run the service should be stopped. But when all tasks has
* finished there might still be more tasks in the message queue waiting to be processed,
@@ -141,7 +160,7 @@
super.onCreate();
mWakeLock = getSystemService(PowerManager.class)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
- mWakeLock.acquire();
+ mWakeLock.setReferenceCounted(false);
HandlerThread thread = new HandlerThread("VvmTaskSchedulerService");
thread.start();
@@ -159,12 +178,20 @@
@MainThread
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Assert.isMainThread();
- Task task = createTask(intent, flags, startId);
- if (task == null) {
- VvmLog.e(TAG, "cannot create task form intent");
+ // maybeRunNextTask() will release the wakelock either by entering a long sleep or stopping
+ // the service.
+ mWakeLock.acquire();
+ if (ACTION_WAKEUP.equals(intent.getAction())) {
+ VvmLog.d(TAG, "woke up by AlarmManager");
} else {
- addTask(task);
+ Task task = createTask(intent, flags, startId);
+ if (task == null) {
+ VvmLog.e(TAG, "cannot create task form intent");
+ } else {
+ addTask(task);
+ }
}
+ maybeRunNextTask();
// STICKY means the service will be automatically restarted will the last intent if it is
// killed.
return START_NOT_STICKY;
@@ -187,7 +214,6 @@
mMainThreadHandler.removeCallbacks(mStopServiceWithDelay);
getTasks().add(task);
maybeRunNextTask();
-
}
@MainThread
@@ -258,6 +284,8 @@
@MainThread
void runNextTask() {
Assert.isMainThread();
+ // The current alarm is no longer valid, a new one will be set up if required.
+ getSystemService(AlarmManager.class).cancel(getWakeupIntent());
if (getTasks().isEmpty()) {
prepareStop();
return;
@@ -280,15 +308,37 @@
}
VvmLog.d(TAG, "minimal wait time:" + minimalWaitTime);
if (!mTaskAutoRunDisabledForTesting && minimalWaitTime != null) {
- // No tests are currently ready. Sleep until the next one should be.
+ // No tasks are currently ready. Sleep until the next one should be.
// If a new task is added during the sleep the service will wake immediately.
+ sleep(minimalWaitTime);
+ }
+ }
+
+ private void sleep(long timeMillis) {
+ if (timeMillis < SHORT_SLEEP_THRESHOLD_MILLISECONDS) {
mMainThreadHandler.postDelayed(new Runnable() {
@Override
public void run() {
maybeRunNextTask();
}
- }, minimalWaitTime);
+ }, timeMillis);
+ return;
}
+
+ // Tasks does not have a strict timing requirement, use AlarmManager.set() so the OS could
+ // optimize the battery usage. As this service currently run in the telephony process the
+ // OS give it privileges to behave the same as setExact(), but set() is the targeted
+ // behavior once this is unbundled.
+ getSystemService(AlarmManager.class).set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + timeMillis,
+ getWakeupIntent());
+ mWakeLock.release();
+ VvmLog.d(TAG, "Long sleep for " + timeMillis + " millis");
+ }
+
+ private PendingIntent getWakeupIntent() {
+ Intent intent = new Intent(ACTION_WAKEUP, null, this, getClass());
+ return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
}
private void prepareStop() {
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java b/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
index 2c37dd9..2f3cbfa 100644
--- a/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
+++ b/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
@@ -17,13 +17,17 @@
package com.android.phone.vvm.omtp.sms;
import android.annotation.MainThread;
+import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.provider.VoicemailContract;
+import android.telephony.SmsManager;
import com.android.phone.Assert;
import com.android.phone.vvm.omtp.OmtpConstants;
import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
@@ -31,6 +35,7 @@
import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
import java.io.Closeable;
import java.io.IOException;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -44,6 +49,8 @@
private static final String TAG = "VvmStatusSmsFetcher";
private static final long STATUS_SMS_TIMEOUT_MILLIS = 60_000;
+ private static final String ACTION_REQUEST_SENT_INTENT = "action_request_sent_intent";
+ private static final int ACTION_REQUEST_SENT_REQUEST_CODE = 0;
private CompletableFuture<Bundle> mFuture = new CompletableFuture<>();
@@ -54,6 +61,7 @@
mContext = context;
mSubId = subId;
IntentFilter filter = new IntentFilter(VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED);
+ filter.addAction(ACTION_REQUEST_SENT_INTENT);
context.registerReceiver(this, filter);
}
@@ -63,16 +71,38 @@
}
@WorkerThread
- public Bundle get()
- throws InterruptedException, ExecutionException, TimeoutException {
+ @Nullable
+ public Bundle get() throws InterruptedException, ExecutionException, TimeoutException,
+ CancellationException {
Assert.isNotMainThread();
return mFuture.get(STATUS_SMS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
+ public PendingIntent getSentIntent() {
+ Intent intent = new Intent(ACTION_REQUEST_SENT_INTENT);
+ // Because the receiver is registered dynamically, implicit intent must be used.
+ // There should only be a single status SMS request at a time.
+ return PendingIntent.getBroadcast(mContext, ACTION_REQUEST_SENT_REQUEST_CODE, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
@Override
@MainThread
public void onReceive(Context context, Intent intent) {
Assert.isMainThread();
+ if (ACTION_REQUEST_SENT_INTENT.equals(intent.getAction())) {
+ int resultCode = getResultCode();
+
+ if (resultCode == Activity.RESULT_OK) {
+ VvmLog.d(TAG, "Request SMS successfully sent");
+ return;
+ }
+
+ VvmLog.e(TAG, "Request SMS send failed: " + sentSmsResultToString(resultCode));
+ mFuture.cancel(true);
+ return;
+ }
+
int subId = intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID);
if (mSubId != subId) {
@@ -105,4 +135,21 @@
mFuture.complete(translatedBundle);
}
}
+
+ private static String sentSmsResultToString(int resultCode) {
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ return "OK";
+ case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
+ return "RESULT_ERROR_GENERIC_FAILURE";
+ case SmsManager.RESULT_ERROR_NO_SERVICE:
+ return "RESULT_ERROR_GENERIC_FAILURE";
+ case SmsManager.RESULT_ERROR_NULL_PDU:
+ return "RESULT_ERROR_GENERIC_FAILURE";
+ case SmsManager.RESULT_ERROR_RADIO_OFF:
+ return "RESULT_ERROR_GENERIC_FAILURE";
+ default:
+ return "UNKNOWN CODE: " + resultCode;
+ }
+ }
}