AOSP/Email - Fixed - Security Vulnerability - Email App: Malicious app
is able to compose message with hidden attachments and bypass
attachments path checks attaching private files from
/data/data/com.android.email/*

+ Ported the following CLs. Code is different from gmail. Made the changes
  to work with Email.
  ++ https://critique.corp.google.com/#review/136780360
     +++ Add isExternal() to ComposeActivity.java and it always returns false.
         Treat body and quoted text as plaintext if intent is external.

  ++ https://critique.corp.google.com/#review/137654162
     +++ Don't let other apps use our EXTRA_MESSAGE.
         Load EXTRA_MESSAGE only if action is LAUNCH_COMPOSE.
         LAUNCH_COMPOSE action is an internal only action: b/32068883.

  ++ https://critique.corp.google.com/#review/142296051
     +++ Don't let external Intent use EXTRA_MESSAGE

Bug: 32068883
Bug: 32502421
Bug: 32589229

Test: manual - Ran the following tests on Pixel phone. Tested the Email UI.

$ adb install -r out/target/product/marlin/system/app/Email/Email.apk

$ adb install -r  app-debug.apk
   Success

$ adb shell am start -n com.test.poc.poc32589229/.MainActivity -a android.intent.action.MAIN
  Starting: Intent { act=android.intent.action.MAIN cmp=com.test.poc.poc32589229/.MainActivity }

  Duplicated the steps in https://b.corp.google.com/issues/32589229#comment5
  and didn't get the attachments after the fix (was getting attachments before the fix).

  logcat output:
    11-21 03:45:48.927 11705 11705 I poc-test: sending a hidden file attachment
    11-21 03:45:48.929 11705 11705 I poc-test: Sending contentType: text/html, previewImage: null
    11-21 03:45:48.935   914  4482 I ActivityManager: START u0 {act=com.android.mail.intent.action.LAUNCH_COMPOSE pkg=com.android.email cmp=com.android.email/.activity.ComposeActivityEmail (has extras)} from uid 10072
    11-21 03:45:48.935   914  4482 W ActivityManager: Permission Denial: starting Intent { act=com.android.mail.intent.action.LAUNCH_COMPOSE pkg=com.android.email cmp=com.android.email/.activity.ComposeActivityEmail (has extras) } from ProcessRecord{6941817 11705:com.test.poc.poc32589229/u0a72} (pid=11705, uid=10072) not exported from uid 10062
    11-21 03:45:48.937 11705 11705 D AndroidRuntime: Shutting down VM
    --------- beginning of crash
    11-21 03:45:48.940 11705 11705 E AndroidRuntime: FATAL EXCEPTION: main
    11-21 03:45:48.940 11705 11705 E AndroidRuntime: Process: com.test.poc.poc32589229, PID: 11705
    11-21 03:45:48.940 11705 11705 E AndroidRuntime: java.lang.IllegalStateException: Could not execute method for android:onClick
    ...
    11-21 03:45:48.940 11705 11705 E AndroidRuntime: Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=com.android.mail.intent.action.LAUNCH_COMPOSE pkg=com.android.email cmp=com.android.email/.activity.ComposeActivityEmail (has extras) } from ProcessRecord{6941817 11705:com.test.poc.poc32589229/u0a72} (pid=11705, uid=10072) not exported from uid 10062

$ adb install -r out/target/product/marlin/testcases/EmailTests/EmailTests.apk
  Performing Streamed Install
  Success

$ adb shell am instrument -w com.android.email.tests
  The number of failures are same as before (with or without this change).
  Tests run: 158,  Failures: 5

Change-Id: If6e2a2efa08b75675c980b72735cde8252e95760
(cherry picked from commit 3526a4ac552f93a83ea838ddae5de45e1e068af0)
diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java
index 3dd9e87..657f4aa 100644
--- a/src/com/android/mail/compose/ComposeActivity.java
+++ b/src/com/android/mail/compose/ComposeActivity.java
@@ -148,7 +148,8 @@
      * An {@link Intent} action that launches {@link ComposeActivity}, but is handled as if the
      * {@link Activity} were launched with no special action.
      */
-    private static final String ACTION_LAUNCH_COMPOSE =
+    @VisibleForTesting
+    static final String ACTION_LAUNCH_COMPOSE =
             "com.android.mail.intent.action.LAUNCH_COMPOSE";
 
     // Identifiers for which type of composition this is
@@ -510,6 +511,11 @@
         context.startActivity(intent);
     }
 
+    /** Returns true if activity is started from an intent from an external application. */
+    public boolean isExternal() {
+        return false;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -529,6 +535,16 @@
         checkValidAccounts();
     }
 
+    /** Used for escaping plaintext. If the input is null, then it will return an empty String. */
+    private static String escapeAndReplaceHtml(CharSequence text) {
+      if (text == null) {
+        return "";
+      }
+      String body = Html.escapeHtml(text);
+      // Replace \r\n and \n with <br> tags
+      return body.replaceAll("(&#13;&#10;|&#10;)", "<br>");
+    }
+
     private void finishCreate() {
         final Bundle savedState = mInnerSavedState;
         findViews();
@@ -567,6 +583,9 @@
             message = intent.getParcelableExtra(ORIGINAL_DRAFT_MESSAGE);
             previews = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENT_PREVIEWS);
             mRefMessage = intent.getParcelableExtra(EXTRA_IN_REFERENCE_TO_MESSAGE);
+            if (isExternal() && mRefMessage != null && !TextUtils.isEmpty(mRefMessage.bodyHtml)) {
+                mRefMessage.bodyHtml = escapeAndReplaceHtml(mRefMessage.bodyHtml);
+            }
             mRefMessageUri = intent.getParcelableExtra(EXTRA_IN_REFERENCE_TO_MESSAGE_URI);
             quotedText = null;
 
@@ -1533,6 +1552,9 @@
                 }
                 String body = intent.getStringExtra(EXTRA_BODY);
                 if (body != null) {
+                    if (isExternal()) {
+                        body = escapeAndReplaceHtml(body);
+                    }
                     setBody(body, false /* withSignature */);
                 }
             }
@@ -1696,7 +1718,10 @@
                 } else if (EXTRA_BODY.equals(extra)) {
                     setBody(value, true /* with signature */);
                 } else if (EXTRA_QUOTED_TEXT.equals(extra)) {
-                    initQuotedText(value, true /* shouldQuoteText */);
+                     if (isExternal()) {
+                         value = escapeAndReplaceHtml(value);
+                     }
+                     initQuotedText(value, true /* shouldQuoteText */);
                 }
             }
         }