Android denial of service attack using class 0 SMS messages

Bug 9702645
Bug 11970403 - Change to ClassZeroActivity to singleTask

Cherry pick the change from master.

Making every AlertDialog immediately visible can lead to exhaustion
of graphics-related resources, typically memory, resulting in a
broken bufferqueue/hw renderer, and subsequent system crash.

Make ClassZeroActivity a singleTask activity, and queue incoming
messages if one is already being displayed.

Change-Id: I0aef7b857364acc404e79581ba5e503571065483
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a34ff9f..13a84d8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -181,6 +181,8 @@
         <activity android:name=".ui.ClassZeroActivity"
             android:label="@string/class_0_message_activity"
             android:theme="@android:style/Theme.Translucent"
+            android:configChanges="orientation|screenSize|keyboardHidden"
+            android:launchMode="singleTask"
             android:excludeFromRecents="true">
         </activity>
 
diff --git a/src/com/android/mms/ui/ClassZeroActivity.java b/src/com/android/mms/ui/ClassZeroActivity.java
index 0abcdda..126e737 100644
--- a/src/com/android/mms/ui/ClassZeroActivity.java
+++ b/src/com/android/mms/ui/ClassZeroActivity.java
@@ -24,6 +24,7 @@
 import android.content.ContentValues;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
 import android.database.Cursor;
 import android.database.sqlite.SqliteWrapper;
 import android.net.Uri;
@@ -41,6 +42,8 @@
 import com.android.mms.R;
 import com.android.mms.transaction.MessagingNotification;
 
+import java.util.ArrayList;
+
 /**
  * Display a class-zero SMS message to the user. Wait for the user to dismiss
  * it.
@@ -69,6 +72,8 @@
     private long mTimerSet = 0;
     private AlertDialog mDialog = null;
 
+    private ArrayList<SmsMessage> mMessageQueue = null;
+
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -77,11 +82,35 @@
                 mRead = false;
                 mDialog.dismiss();
                 saveMessage();
-                finish();
+                processNextMessage();
             }
         }
     };
 
+    private boolean queueMsgFromIntent(Intent msgIntent) {
+        byte[] pdu = msgIntent.getByteArrayExtra("pdu");
+        String format = msgIntent.getStringExtra("format");
+        SmsMessage rawMessage = SmsMessage.createFromPdu(pdu, format);
+        String message = rawMessage.getMessageBody();
+        if (TextUtils.isEmpty(message)) {
+            if (mMessageQueue.size() == 0) {
+                finish();
+            }
+            return false;
+        }
+        mMessageQueue.add(rawMessage);
+        return true;
+    }
+
+    private void processNextMessage() {
+        mMessageQueue.remove(0);
+        if (mMessageQueue.size() == 0) {
+            finish();
+        } else {
+            displayZeroMessage(mMessageQueue.get(0));
+        }
+    }
+
     private void saveMessage() {
         Uri messageUri = null;
         if (mMessage.isReplace()) {
@@ -98,37 +127,48 @@
     }
 
     @Override
+    protected void onNewIntent(Intent msgIntent) {
+        /* Running with another visible message, queue this one */
+        queueMsgFromIntent(msgIntent);
+    }
+
+    @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         getWindow().setBackgroundDrawableResource(
                 R.drawable.class_zero_background);
 
-        byte[] pdu = getIntent().getByteArrayExtra("pdu");
-        String format = getIntent().getStringExtra("format");
-        mMessage = SmsMessage.createFromPdu(pdu, format);
-        CharSequence messageChars = mMessage.getMessageBody();
-        String message = messageChars.toString();
-        if (TextUtils.isEmpty(message)) {
-            finish();
+        if (mMessageQueue == null) {
+            mMessageQueue = new ArrayList<SmsMessage>();
+        }
+
+        if (!queueMsgFromIntent(getIntent())) {
             return;
         }
-        // TODO: The following line adds an emptry string before and after a message.
-        // This is not the correct way to layout a message. This is more of a hack
-        // to work-around a bug in AlertDialog. This needs to be fixed later when
-        // Android fixes the bug in AlertDialog.
-        if (message.length() < BUFFER_OFFSET) messageChars = BUFFER + message + BUFFER;
-        long now = SystemClock.uptimeMillis();
-        mDialog = new AlertDialog.Builder(this).setMessage(messageChars)
-                .setPositiveButton(R.string.save, mSaveListener)
-                .setNegativeButton(android.R.string.cancel, mCancelListener)
-                .setCancelable(false).show();
-        mTimerSet = now + DEFAULT_TIMER;
+
+        if (mMessageQueue.size() == 1) {
+            displayZeroMessage(mMessageQueue.get(0));
+        }
+
         if (icicle != null) {
             mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet);
         }
     }
 
+    private void displayZeroMessage(SmsMessage rawMessage) {
+        String message = rawMessage.getMessageBody();
+        /* This'll be used by the save action */
+        mMessage = rawMessage;
+
+        mDialog = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK).setMessage(message)
+                .setPositiveButton(R.string.save, mSaveListener)
+                .setNegativeButton(android.R.string.cancel, mCancelListener)
+                .setCancelable(false).show();
+        long now = SystemClock.uptimeMillis();
+        mTimerSet = now + DEFAULT_TIMER;
+    }
+
     @Override
     protected void onStart() {
         super.onStart();
@@ -168,7 +208,7 @@
     private final OnClickListener mCancelListener = new OnClickListener() {
         public void onClick(DialogInterface dialog, int whichButton) {
             dialog.dismiss();
-            finish();
+            processNextMessage();
         }
     };
 
@@ -177,7 +217,7 @@
             mRead = true;
             saveMessage();
             dialog.dismiss();
-            finish();
+            processNextMessage();
         }
     };