Merge "Added privileged permissions for CellBroadcastAppPlatform app build rule."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7120f10..3005a8c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -87,6 +87,18 @@
</intent-filter>
</activity>
+ <!-- Alias activity for CellBroadcastListActivity. Once enabled, it will appear in the launcher -->
+ <activity-alias android:name="com.android.cellbroadcastreceiver.CellBroadcastListLauncherActivity"
+ android:targetActivity="com.android.cellbroadcastreceiver.CellBroadcastListActivity"
+ android:enabled="false"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity-alias>
+
<!-- Settings opened by ListActivity menu, Settings app link or opt-out dialog. -->
<activity android:name="CellBroadcastSettings"
android:theme="@style/CellBroadcastSettingsTheme"
@@ -117,6 +129,7 @@
<action android:name="android.cellbroadcastreceiver.START_CONFIG" />
<action android:name="android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
+ <action android:name="android.intent.action.SERVICE_STATE" />
</intent-filter>
<intent-filter>
<action android:name="android.telephony.action.SECRET_CODE" />
diff --git a/AndroidManifest_Platform.xml b/AndroidManifest_Platform.xml
index 45751fd..6eb9c86 100644
--- a/AndroidManifest_Platform.xml
+++ b/AndroidManifest_Platform.xml
@@ -83,6 +83,18 @@
</intent-filter>
</activity>
+ <!-- Alias activity for CellBroadcastListActivity. Once enabled, it will appear in the launcher -->
+ <activity-alias android:name="CellBroadcastListLauncherActivity"
+ android:targetActivity="CellBroadcastListActivity"
+ android:enabled="false"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity-alias>
+
<!-- Settings opened by ListActivity menu, Settings app link or opt-out dialog. -->
<activity android:name="CellBroadcastSettings"
android:theme="@style/CellBroadcastSettingsTheme"
@@ -113,6 +125,7 @@
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED" />
<action android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
+ <action android:name="android.intent.action.SERVICE_STATE" />
</intent-filter>
<intent-filter>
<action android:name="android.telephony.action.SECRET_CODE" />
diff --git a/res/values-mcc450/config.xml b/res/values-mcc450/config.xml
index 53ed91f..96b6813 100644
--- a/res/values-mcc450/config.xml
+++ b/res/values-mcc450/config.xml
@@ -38,7 +38,7 @@
<!-- 4373~4379, 40960~45055-->
<string-array name="additional_cbs_channels_strings" translatable="false">
<item>0x1115-0x111B:rat=gsm, emergency=true, alert_duration=60000</item>
- <item>0xA000-0xAFFF:rat=gsm, emergency=false</item>
+ <item>0xA000-0xAFFF:rat=gsm, emergency=false, scope=carrier, exclude_from_sms_inbox=true</item>
</string-array>
<string-array name="cmas_alert_extreme_channels_range_strings" translatable="false"></string-array>
diff --git a/res/values/config.xml b/res/values/config.xml
index 27981c1..1aa4d10 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -80,6 +80,8 @@
<!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
<!-- TODO: we have another copy of this config in framework which is consumed by settings. should clean this up as part of brazil 50 refactor -->
<bool name="config_showAreaUpdateInfoSettings">false</bool>
+ <!-- Show cellbroadcast message history entry point in the application launcher.-->
+ <bool name="show_message_history_in_launcher">false</bool>
<!-- Specify second language code to receive emergency alerts -->
<string name="emergency_alert_second_language_code" translatable="false"></string>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index 29d6029..8640187 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -38,11 +38,15 @@
<item type="color" name="cell_broadcast_color_primary_dark"/>
<!-- END COLOR -->
+ <!-- Customize launcher icon for cellbroadcast history -->
+ <item type="mipmap" name="ic_launcher_cell_broadcast" />
+
<!-- START CHANNEL related configurations which allows customization -->
<!-- Presidential alert is default on and cannot be turned off.
AOSP CBR app does not show presidential alert in settings, however OEM can choose to
display a greyout presidential alert settings by overriding below config -->
<item type="bool" name="show_presidential_alerts_settings" />
+ <item type="bool" name="show_message_history_in_launcher" />
<!-- Some JP carriers requires to enable 0xA807 in additional_cbs_channels_strings
but only for certain products. OEMs can customize below config by including 0xA807
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
index bcfc3e4..62a8bbd 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
@@ -130,7 +130,8 @@
private static final int KEEP_SCREEN_ON_DURATION_MSEC = 60000;
/** Animation handler for the flashing warning icon (emergency alerts only). */
- private final AnimationHandler mAnimationHandler = new AnimationHandler();
+ @VisibleForTesting
+ public AnimationHandler mAnimationHandler = new AnimationHandler();
/** Handler to add and remove screen on flags for emergency alerts. */
private final ScreenOffHandler mScreenOffHandler = new ScreenOffHandler();
@@ -141,12 +142,15 @@
/**
* Animation handler for the flashing warning icon (emergency alerts only).
*/
- private class AnimationHandler extends Handler {
+ @VisibleForTesting
+ public class AnimationHandler extends Handler {
/** Latest {@code message.what} value for detecting old messages. */
- private final AtomicInteger mCount = new AtomicInteger();
+ @VisibleForTesting
+ public final AtomicInteger mCount = new AtomicInteger();
/** Warning icon state: visible == true, hidden == false. */
- private boolean mWarningIconVisible;
+ @VisibleForTesting
+ public boolean mWarningIconVisible;
/** The warning icon Drawable. */
private Drawable mWarningIcon;
@@ -158,7 +162,8 @@
AnimationHandler() {}
/** Start the warning icon animation. */
- void startIconAnimation(int subId) {
+ @VisibleForTesting
+ public void startIconAnimation(int subId) {
if (!initDrawableAndImageView(subId)) {
return; // init failure
}
@@ -169,7 +174,8 @@
}
/** Stop the warning icon animation. */
- void stopIconAnimation() {
+ @VisibleForTesting
+ public void stopIconAnimation() {
// Increment the counter so the handler will ignore the next message.
mCount.incrementAndGet();
if (mWarningIconView != null) {
@@ -354,7 +360,8 @@
if (res.getBoolean(R.bool.enable_text_copy)) {
TextView textView = findViewById(R.id.message);
if (textView != null) {
- textView.setOnLongClickListener(v -> copyMessageToClipboard(message));
+ textView.setOnLongClickListener(v -> copyMessageToClipboard(message,
+ getApplicationContext()));
}
}
}
@@ -364,7 +371,8 @@
* Start animating warning icon.
*/
@Override
- protected void onResume() {
+ @VisibleForTesting
+ public void onResume() {
super.onResume();
SmsCbMessage message = getLatestMessage();
if (message != null) {
@@ -381,7 +389,8 @@
* Stop animating warning icon.
*/
@Override
- protected void onPause() {
+ @VisibleForTesting
+ public void onPause() {
Log.d(TAG, "onPause called");
mAnimationHandler.stopIconAnimation();
super.onPause();
@@ -807,15 +816,16 @@
*
* @return {@code true} if success, otherwise {@code false};
*/
- private boolean copyMessageToClipboard(SmsCbMessage message) {
- ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ @VisibleForTesting
+ public static boolean copyMessageToClipboard(SmsCbMessage message, Context context) {
+ ClipboardManager cm = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
if (cm == null) return false;
cm.setPrimaryClip(ClipData.newPlainText("Alert Message", message.getMessageBody()));
- String msg = CellBroadcastSettings.getResources(getApplicationContext(),
+ String msg = CellBroadcastSettings.getResources(context,
message.getSubscriptionId()).getString(R.string.message_copied);
- Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
return true;
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
index 82a5fd1..1e9a16f 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -291,7 +291,16 @@
if (CellBroadcastSettings.getResources(mContext,
message.getSubscriptionId())
.getBoolean(R.bool.enable_write_alerts_to_sms_inbox)) {
- writeMessageToSmsInbox(message);
+ // TODO: Should not create the instance of channel manager everywhere.
+ CellBroadcastChannelManager channelManager =
+ new CellBroadcastChannelManager(mContext,
+ message.getSubscriptionId());
+ CellBroadcastChannelRange range = channelManager
+ .getCellBroadcastChannelRangeFromMessage(message);
+ if (CellBroadcastReceiver.isTestingMode(getApplicationContext())
+ || range.mWriteToSmsInbox) {
+ writeMessageToSmsInbox(message);
+ }
}
return true;
} else {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java b/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
index e25a6fb..178b026 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
@@ -92,6 +92,8 @@
private static final String KEY_ALERT_DURATION = "alert_duration";
/** Defines if Do Not Disturb should be overridden for this alert */
private static final String KEY_OVERRIDE_DND = "override_dnd";
+ /** Defines whether writing alert message should exclude from SMS inbox. */
+ private static final String KEY_EXCLUDE_FROM_SMS_INBOX = "exclude_from_sms_inbox";
/**
* Defines whether the channel needs language filter or not. True indicates that the alert
@@ -120,6 +122,9 @@
// by default no custom alert duration. play the alert tone with the tone's duration.
public int mAlertDuration = -1;
public boolean mOverrideDnd = false;
+ // If enable_write_alerts_to_sms_inbox is true, write to sms inbox is enabled by default
+ // for all channels except for channels which explicitly set to exclude from sms inbox.
+ public boolean mWriteToSmsInbox = true;
public CellBroadcastChannelRange(Context context, int subId, String channelRange) {
@@ -189,6 +194,11 @@
mOverrideDnd = true;
}
break;
+ case KEY_EXCLUDE_FROM_SMS_INBOX:
+ if (value.equalsIgnoreCase("true")) {
+ mWriteToSmsInbox = false;
+ }
+ break;
}
}
}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
index fd0198a..386c59f 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
@@ -19,11 +19,15 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.RemoteException;
@@ -33,9 +37,11 @@
import android.provider.Telephony;
import android.provider.Telephony.CellBroadcasts;
import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
@@ -56,6 +62,12 @@
// Key to access the shared preference of cell broadcast testing mode.
private static final String TESTING_MODE = "testing_mode";
+ // Key to access the shared preference of service state.
+ private static final String SERVICE_STATE = "service_state";
+
+ public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
+ public static final String EXTRA_VOICE_REG_STATE = "voiceRegState";
+
// Intent actions and extras
public static final String CELLBROADCAST_START_CONFIG_ACTION =
"com.android.cellbroadcastreceiver.intent.START_CONFIG";
@@ -109,7 +121,18 @@
getCellBroadcastTask(deliveryTime);
} else if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
initializeSharedPreference();
+ enableLauncher();
startConfigService();
+ } else if (ACTION_SERVICE_STATE.equals(action)) {
+ // lower layer clears channel configurations under APM, thus need to resend
+ // configurations once moving back from APM. This should be fixed in lower layer
+ // going forward.
+ int ss = intent.getIntExtra(EXTRA_VOICE_REG_STATE, ServiceState.STATE_IN_SERVICE);
+ if (ss != ServiceState.STATE_POWER_OFF
+ && getServiceState(context) == ServiceState.STATE_POWER_OFF) {
+ startConfigService();
+ }
+ setServiceState(ss);
} else if (CELLBROADCAST_START_CONFIG_ACTION.equals(action)
|| SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED.equals(action)) {
startConfigService();
@@ -167,6 +190,24 @@
}
/**
+ * Store the current service state for voice registration.
+ *
+ * @param ss current voice registration service state.
+ */
+ private void setServiceState(int ss) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ sp.edit().putInt(SERVICE_STATE, ss).commit();
+ }
+
+ /**
+ * @return the stored voice registration service state
+ */
+ private static int getServiceState(Context context) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ return sp.getInt(SERVICE_STATE, ServiceState.STATE_IN_SERVICE);
+ }
+
+ /**
* update reminder interval
*/
@VisibleForTesting
@@ -404,6 +445,51 @@
}
}
+ /**
+ * Enable Launcher.
+ */
+ @VisibleForTesting
+ public void enableLauncher() {
+ boolean enable = getResourcesMethod().getBoolean(R.bool.show_message_history_in_launcher);
+ final PackageManager pm = mContext.getPackageManager();
+ // This alias presents the target activity, CellBroadcastListActivity, as a independent
+ // entity with its own intent filter for android.intent.category.LAUNCHER.
+ // This alias will be enabled/disabled at run-time based on resource overlay. Once enabled,
+ // it will appear in the Launcher as a top-level application
+ String aliasLauncherActivity = null;
+ try {
+ PackageInfo p = pm.getPackageInfo(mContext.getPackageName(),
+ PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (p != null) {
+ for (ActivityInfo activityInfo : p.activities) {
+ String targetActivity = activityInfo.targetActivity;
+ if (CellBroadcastListActivity.class.getName().equals(targetActivity)) {
+ aliasLauncherActivity = activityInfo.name;
+ break;
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, e.toString());
+ }
+ if (TextUtils.isEmpty(aliasLauncherActivity)) {
+ Log.e(TAG, "cannot find launcher activity");
+ return;
+ }
+
+ if (enable) {
+ Log.d(TAG, "enable launcher activity: " + aliasLauncherActivity);
+ pm.setComponentEnabledSetting(
+ new ComponentName(mContext, aliasLauncherActivity),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ } else {
+ Log.d(TAG, "disable launcher activity: " + aliasLauncherActivity);
+ pm.setComponentEnabledSetting(
+ new ComponentName(mContext, aliasLauncherActivity),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+ }
+
private static void log(String msg) {
Log.d(TAG, msg);
}
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertDialogTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertDialogTest.java
index ad9b9f8..ecad843 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertDialogTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertDialogTest.java
@@ -16,9 +16,11 @@
package com.android.cellbroadcastreceiver.unit;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -29,9 +31,11 @@
import android.os.Bundle;
import android.os.IPowerManager;
import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.telephony.SmsCbMessage;
+import android.view.KeyEvent;
import android.view.WindowManager;
import android.widget.TextView;
@@ -65,6 +69,7 @@
private ArgumentCaptor<Notification> mNotification;
private PowerManager mPowerManager;
+ private int mSubId = 0;
public CellBroadcastAlertDialogTest() {
super(CellBroadcastAlertDialog.class);
@@ -211,4 +216,61 @@
verify(mMockedNotificationManager, atLeastOnce()).cancel(
eq(CellBroadcastAlertService.NOTIFICATION_ID));
}
+
+ public void testAnimationHandler() throws Throwable {
+ CellBroadcastAlertDialog activity = startActivity();
+ CellBroadcastSettings.setUseResourcesForSubId(false);
+
+ activity.mAnimationHandler.startIconAnimation(mSubId);
+
+ assertTrue(activity.mAnimationHandler.mWarningIconVisible);
+
+ Message m = Message.obtain();
+ m.what = activity.mAnimationHandler.mCount.get();
+ activity.mAnimationHandler.handleMessage(m);
+
+ // assert that message count has gone up
+ assertEquals(m.what + 1, activity.mAnimationHandler.mCount.get());
+ }
+
+ public void testOnResume() throws Throwable {
+ Intent intent = createActivityIntent();
+ intent.putExtra(CellBroadcastAlertDialog.FROM_NOTIFICATION_EXTRA, true);
+
+ Looper.prepare();
+ CellBroadcastAlertDialog activity = startActivity(intent, null, null);
+
+ CellBroadcastAlertDialog.AnimationHandler mockAnimationHandler = mock(
+ CellBroadcastAlertDialog.AnimationHandler.class);
+ activity.mAnimationHandler = mockAnimationHandler;
+
+ activity.onResume();
+ verify(mockAnimationHandler).startIconAnimation(anyInt());
+ }
+
+ public void testOnPause() throws Throwable {
+ Intent intent = createActivityIntent();
+ intent.putExtra(CellBroadcastAlertDialog.FROM_NOTIFICATION_EXTRA, true);
+
+ Looper.prepare();
+ CellBroadcastAlertDialog activity = startActivity(intent, null, null);
+
+ CellBroadcastAlertDialog.AnimationHandler mockAnimationHandler = mock(
+ CellBroadcastAlertDialog.AnimationHandler.class);
+ activity.mAnimationHandler = mockAnimationHandler;
+
+ activity.onPause();
+ verify(mockAnimationHandler).stopIconAnimation();
+ }
+
+ public void testOnKeyDown() throws Throwable {
+ Intent intent = createActivityIntent();
+ intent.putExtra(CellBroadcastAlertDialog.FROM_NOTIFICATION_EXTRA, true);
+
+ Looper.prepare();
+ CellBroadcastAlertDialog activity = startActivity(intent, null, null);
+
+ assertTrue(activity.onKeyDown(0,
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS)));
+ }
}
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java
index 55172a8..95cb837 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java
@@ -111,9 +111,11 @@
public void testOnReceive_actionCarrierConfigChanged() {
doReturn(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED).when(mIntent).getAction();
doNothing().when(mCellBroadcastReceiver).initializeSharedPreference();
+ doNothing().when(mCellBroadcastReceiver).enableLauncher();
mCellBroadcastReceiver.onReceive(mContext, mIntent);
verify(mCellBroadcastReceiver).initializeSharedPreference();
verify(mCellBroadcastReceiver).startConfigService();
+ verify(mCellBroadcastReceiver).enableLauncher();
}
@Test