DO NOT MERGE Defer VVM activation/deactivation until device is unlocked.
VVM activation/deactivation requires voicemail status table update and
sending SMS which is not available if the device is locked. The
activation/deactivation also requires the subId of the SIM card, which
is enumerated when the SIM loads, with the possibility of the device
still being locked.
This CL checks if the device is locked when SimChangeReceiver is
triggered. If so, the subId will be stored by DeferredSimChangeProcessor
and resent to SimChangeReceiver after the device is unlocked.
VVM SYNC SMS could also be received when the device is locked. The SMS
will simply be ignored, as a full sync will always be performed when the
device in unlocked.
Bug:27534089
Bug:26261432
Change-Id: I9a966e5b1b880a764e4ca6b18e77796a88496043
(cherry picked from commit 0d515a7e18ce04c42df7acb3160529d56cfebd80)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 47987fa..2335d77 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -662,13 +662,22 @@
<data android:scheme="sms" />
</intent-filter>
</receiver>
- <receiver android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
+ <receiver
+ android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
<action android:name="android.intent.action.SIM_STATE_CHANGED" />
</intent-filter>
- </receiver>
+ </receiver>
+ <receiver
+ android:name="com.android.phone.vvm.omtp.OmtpBootCompletedReceiver"
+ android:exported="true"
+ android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
<receiver
android:name="com.android.phone.vvm.omtp.fetch.FetchVoicemailReceiver"
android:exported="true">
diff --git a/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java b/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java
new file mode 100644
index 0000000..a751e19
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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.vvm.omtp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Set;
+
+/**
+ * Stores subscription ID of SIMs while the device is locked to process them after the device is
+ * unlocked. This class is only intended to be used within {@link SimChangeReceiver}. subId is used
+ * for Visual voicemail activation/deactivation, which need to be done when the device is unlocked.
+ * But the enumeration of subIds happen on boot, when the device could be locked. This class is used
+ * to defer all activation/deactivation until the device is unlocked.
+ *
+ * The subIds are stored in device encrypted {@link SharedPreferences} (readable/writable even
+ * locked). after the device is unlocked the list is read and deleted.
+ */
+public class OmtpBootCompletedReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "OmtpBootCompletedRcvr";
+
+ private static final String DEFERRED_SUBID_LIST_KEY = "deferred_sub_id_key";
+
+ @VisibleForTesting
+ interface SubIdProcessor{
+ void process(Context context,int subId);
+ }
+
+ private SubIdProcessor mSubIdProcessor = new SubIdProcessor() {
+ @Override
+ public void process(Context context, int subId) {
+ SimChangeReceiver.processSubId(context,subId);
+ }
+ };
+
+ /**
+ * Write the subId to the the list.
+ */
+ public static void addDeferredSubId(Context context, int subId) {
+ SharedPreferences sharedPreferences = getSubIdSharedPreference(context);
+ Set<String> subIds =
+ new ArraySet<>(sharedPreferences.getStringSet(DEFERRED_SUBID_LIST_KEY, null));
+ subIds.add(String.valueOf(subId));
+ sharedPreferences.edit().putStringSet(DEFERRED_SUBID_LIST_KEY, subIds).apply();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Listens to android.intent.action.BOOT_COMPLETED
+ if(!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ return;
+ }
+
+ Log.v(TAG, "processing deferred subId list");
+ Set<Integer> subIds = readAndDeleteSubIds(context);
+ for (Integer subId : subIds) {
+ Log.v(TAG, "processing subId " + subId);
+ mSubIdProcessor.process(context, subId);
+ }
+ }
+
+ /**
+ * Read all subId from the list to a unique integer set, and delete the preference.
+ */
+ private static Set<Integer> readAndDeleteSubIds(Context context) {
+ SharedPreferences sharedPreferences = getSubIdSharedPreference(context);
+ Set<String> subIdStrings = sharedPreferences.getStringSet(DEFERRED_SUBID_LIST_KEY, null);
+ Set<Integer> subIds = new ArraySet<>();
+ if(subIdStrings == null) {
+ return subIds;
+ }
+ for(String string : subIdStrings){
+ subIds.add(Integer.valueOf(string));
+ }
+ getSubIdSharedPreference(context).edit().remove(DEFERRED_SUBID_LIST_KEY).apply();
+ return subIds;
+ }
+
+ @VisibleForTesting
+ void setSubIdProcessorForTest(SubIdProcessor processor){
+ mSubIdProcessor = processor;
+ }
+
+ private static SharedPreferences getSubIdSharedPreference(Context context) {
+ return PreferenceManager
+ .getDefaultSharedPreferences(context.createDeviceEncryptedStorageContext());
+ }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 57e23a9..61cf6d3 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -34,7 +34,8 @@
* retrieve carrier vvm configuration details before sending the appropriate texts.
*/
public class OmtpVvmCarrierConfigHelper {
- private static final String TAG = "OmtpVvmCarrierConfigHelper";
+
+ private static final String TAG = "OmtpVvmCarrierCfgHlpr";
private Context mContext;
private int mSubId;
private PersistableBundle mCarrierConfig;
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index c42797a..0a37493 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -34,15 +34,16 @@
import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
/**
- * This class listens to the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} and
- * {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED} to determine when a SIM is added, replaced,
- * or removed.
+ * This class listens to the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} and {@link
+ * TelephonyIntents#ACTION_SIM_STATE_CHANGED} to determine when a SIM is added, replaced, or
+ * removed.
*
* When a SIM is added, send an activate SMS. When a SIM is removed, remove the sync accounts and
* change the status in the voicemail_status table.
*/
public class SimChangeReceiver extends BroadcastReceiver {
- private final String TAG = "SimChangeReceiver";
+
+ private static final String TAG = "SimChangeReceiver";
@Override
public void onReceive(Context context, Intent intent) {
@@ -74,46 +75,54 @@
return;
}
- OmtpVvmCarrierConfigHelper carrierConfigHelper =
- new OmtpVvmCarrierConfigHelper(context, subId);
- if (carrierConfigHelper.isOmtpVvmType()) {
- PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
- SubscriptionManager.getPhoneId(subId));
-
- boolean isUserSet = VisualVoicemailSettingsUtil.isVisualVoicemailUserSet(
- context, phoneAccount);
- boolean isEnabledInSettings =
- VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context,
- phoneAccount);
- boolean isSupported =
- context.getResources().getBoolean(R.bool.allow_visual_voicemail);
- boolean isEnabled = isSupported && (isUserSet ? isEnabledInSettings :
- carrierConfigHelper.isEnabledByDefault());
-
- if (!isUserSet) {
- // Preserve the previous setting for "isVisualVoicemailEnabled" if it is
- // set by the user, otherwise, set this value for the first time.
- VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(context, phoneAccount,
- isEnabled, /** isUserSet */ false);
- }
-
- if (isEnabled) {
- LocalLogHelper.log(TAG, "Sim state or carrier config changed: requesting"
- + " activation for " + phoneAccount.getId());
-
- // Add a phone state listener so that changes to the communication channels
- // can be recorded.
- OmtpVvmSourceManager.getInstance(context).addPhoneStateListener(
- phoneAccount);
- carrierConfigHelper.startActivation();
- } else {
- // It may be that the source was not registered to begin with but we want
- // to run through the steps to remove the source just in case.
- OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
- Log.v(TAG, "Sim change for disabled account.");
- }
+ if (!UserManager.get(context).isUserUnlocked()) {
+ OmtpBootCompletedReceiver.addDeferredSubId(context, subId);
+ } else {
+ processSubId(context, subId);
}
break;
}
}
+
+ public static void processSubId(Context context, int subId) {
+ OmtpVvmCarrierConfigHelper carrierConfigHelper =
+ new OmtpVvmCarrierConfigHelper(context, subId);
+ if (carrierConfigHelper.isOmtpVvmType()) {
+ PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
+ SubscriptionManager.getPhoneId(subId));
+
+ boolean isUserSet = VisualVoicemailSettingsUtil.isVisualVoicemailUserSet(
+ context, phoneAccount);
+ boolean isEnabledInSettings =
+ VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context,
+ phoneAccount);
+ boolean isSupported =
+ context.getResources().getBoolean(R.bool.allow_visual_voicemail);
+ boolean isEnabled = isSupported && (isUserSet ? isEnabledInSettings :
+ carrierConfigHelper.isEnabledByDefault());
+
+ if (!isUserSet) {
+ // Preserve the previous setting for "isVisualVoicemailEnabled" if it is
+ // set by the user, otherwise, set this value for the first time.
+ VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(context, phoneAccount,
+ isEnabled, /** isUserSet */false);
+ }
+
+ if (isEnabled) {
+ LocalLogHelper.log(TAG, "Sim state or carrier config changed: requesting"
+ + " activation for " + phoneAccount.getId());
+
+ // Add a phone state listener so that changes to the communication channels
+ // can be recorded.
+ OmtpVvmSourceManager.getInstance(context).addPhoneStateListener(
+ phoneAccount);
+ carrierConfigHelper.startActivation();
+ } else {
+ // It may be that the source was not registered to begin with but we want
+ // to run through the steps to remove the source just in case.
+ OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
+ Log.v(TAG, "Sim change for disabled account.");
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 0950862..9ac37a4 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -19,16 +19,13 @@
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.NetworkRequest;
import android.net.Uri;
+import android.os.UserManager;
import android.provider.Telephony;
import android.provider.VoicemailContract;
import android.telecom.PhoneAccountHandle;
import android.telecom.Voicemail;
import android.telephony.SmsMessage;
-import android.telephony.SubscriptionManager;
import android.util.Log;
import com.android.internal.telephony.PhoneConstants;
@@ -52,6 +49,12 @@
@Override
public void onReceive(Context context, Intent intent) {
+ if (!UserManager.get(context).isUserUnlocked()) {
+ Log.i(TAG, "Received message on locked device");
+ // A full sync will happen after the device is unlocked, so nothing need to be done.
+ return;
+ }
+
mContext = context;
mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
diff --git a/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java b/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java
new file mode 100644
index 0000000..a2d247a
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 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.vvm.omtp;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+
+import com.android.phone.vvm.omtp.OmtpBootCompletedReceiver.SubIdProcessor;
+
+import java.util.Set;
+
+public class OmtpBootCompletedReceiverTests extends AndroidTestCase {
+ OmtpBootCompletedReceiver mReceiver = new OmtpBootCompletedReceiver();
+ @Override
+ public void setUp() {
+ }
+
+ @Override
+ public void tearDown() {
+ PreferenceManager
+ .getDefaultSharedPreferences(getContext().createDeviceEncryptedStorageContext())
+ .edit().clear().apply();
+ }
+
+ public void testReadWriteList() {
+ readWriteList(new int[] {1});
+ }
+
+ public void testReadWriteList_Multiple() {
+ readWriteList(new int[] {1, 2});
+ }
+
+ public void testReadWriteList_Duplicate() {
+ readWriteList(new int[] {1, 1});
+ }
+
+ private void readWriteList(int[] values) {
+ for (int value : values) {
+ OmtpBootCompletedReceiver.addDeferredSubId(getContext(), value);
+ }
+ TestSubIdProcessor processor = new TestSubIdProcessor(values);
+ mReceiver.setSubIdProcessorForTest(processor);
+ Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+ mReceiver.onReceive(getContext(), intent);
+ processor.assertMatch();
+ // after onReceive() is called the list should be empty
+ TestSubIdProcessor emptyProcessor = new TestSubIdProcessor(new int[] {});
+ mReceiver.setSubIdProcessorForTest(processor);
+ mReceiver.onReceive(getContext(), intent);
+ processor.assertMatch();
+ }
+
+ private static class TestSubIdProcessor implements SubIdProcessor {
+ private final Set<Integer> mExpectedSubIds;
+
+ public TestSubIdProcessor(int[] expectedSubIds) {
+ mExpectedSubIds = new ArraySet<>();
+ for(int subId : expectedSubIds){
+ mExpectedSubIds.add(subId);
+ }
+ }
+
+ @Override
+ public void process(Context context, int subId){
+ assertTrue(mExpectedSubIds.contains(subId));
+ mExpectedSubIds.remove(subId);
+ }
+
+ public void assertMatch(){
+ assertTrue(mExpectedSubIds.isEmpty());
+ }
+ }
+}