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