DO NOT MERGE - Skip qt-dev-plus-aosp-without-vendor (5713463) in stage-aosp-master

Bug: 134405016
Change-Id: I7e836f2692bac2b6bd7c5c83420fde7dfa56ffc4
diff --git a/Android.mk b/Android.mk
index b04fc97..41b04fd 100644
--- a/Android.mk
+++ b/Android.mk
@@ -45,7 +45,6 @@
     libchips \
     libphotoviewer_appcompat \
     android-opt-bitmap \
-    android-opt-datetimepicker \
     androidx.core_core \
     androidx.media_media \
     androidx.legacy_legacy-support-core-utils \
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 71648e8..b4450fc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -45,9 +45,10 @@
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 
     <!-- This needs to be present when we are doing unbundled releases. -->
-    <uses-sdk android:targetSdkVersion="24" android:minSdkVersion="14" />
+    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="14" />
 
     <!-- additional uses -->
 
@@ -486,7 +487,9 @@
             </intent-filter>
         </receiver>
         <service
-            android:name=".service.EmailBroadcastProcessorService" />
+            android:name=".service.EmailBroadcastProcessorService"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:exported="true" />
 
         <!-- Support for DeviceAdmin / DevicePolicyManager.  See SecurityPolicy class for impl. -->
         <receiver
@@ -506,6 +509,7 @@
         <service
             android:name=".service.AttachmentService"
             android:enabled="false"
+            android:permission="android.permission.BIND_JOB_SERVICE"
             >
         </service>
 
@@ -685,6 +689,7 @@
         <provider
             android:name=".provider.EmailProvider"
             android:authorities="com.android.email.provider;com.android.email.notifier"
+            android:enabled="true"
             android:exported="true"
             android:permission="com.android.email.permission.ACCESS_PROVIDER"
             android:label="@string/app_name"
@@ -725,6 +730,7 @@
         </service>
 
         <service android:name="com.android.email.EmailIntentService"
+                 android:permission="android.permission.BIND_JOB_SERVICE"
                  android:exported="false">
             <intent-filter>
                 <action android:name="com.android.mail.action.RESEND_NOTIFICATIONS" />
diff --git a/emailcommon/AndroidManifest.xml b/emailcommon/AndroidManifest.xml
index bd1d35d..88573f7 100644
--- a/emailcommon/AndroidManifest.xml
+++ b/emailcommon/AndroidManifest.xml
@@ -2,5 +2,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.emailcommon"
           android:versionCode="1">
-    <uses-sdk android:targetSdkVersion="24" android:minSdkVersion="14" />
+    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="14" />
 </manifest>
diff --git a/provider_src/com/android/email/EmailIntentService.java b/provider_src/com/android/email/EmailIntentService.java
index 0c6d761..5924c90 100644
--- a/provider_src/com/android/email/EmailIntentService.java
+++ b/provider_src/com/android/email/EmailIntentService.java
@@ -29,12 +29,12 @@
     private static final String LOG_TAG = LogTag.getLogTag();
 
     public EmailIntentService() {
-        super("EmailIntentService");
+        super();
     }
 
     @Override
-    protected void onHandleIntent(final Intent intent) {
-        super.onHandleIntent(intent);
+    protected void onHandleWork(final Intent intent) {
+        super.onHandleWork(intent);
 
         if (UIProvider.ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
             final NotificationController nc =
diff --git a/provider_src/com/android/email/provider/EmailProvider.java b/provider_src/com/android/email/provider/EmailProvider.java
index 00d608f..9dd2d2e 100644
--- a/provider_src/com/android/email/provider/EmailProvider.java
+++ b/provider_src/com/android/email/provider/EmailProvider.java
@@ -1317,8 +1317,10 @@
                             uri.getQueryParameter(EmailContent.SUPPRESS_COMBINED_ACCOUNT_PARAM);
                     final boolean suppressCombined =
                             suppressParam != null && Boolean.parseBoolean(suppressParam);
-                    c = uiAccounts(projection, suppressCombined);
-                    return c;
+                    // TODO(rtenneti): Enable notifications.
+                    // c = uiAccounts(projection, suppressCombined);
+                    // return c;
+                    return null;
                 case UI_UNDO:
                     return uiUndo(projection);
                 case UI_SUBFOLDERS:
@@ -1346,8 +1348,10 @@
                     c = uiQuery(match, uri, projection, unseenOnly);
                     return c;
                 case UI_FOLDERS:
-                    c = uiFolders(uri, projection);
-                    return c;
+                    // TODO(rtenneti): Enable notifications.
+                    // c = uiFolders(uri, projection);
+                    // return c;
+                    return null;
                 case UI_FOLDER_LOAD_MORE:
                     c = uiFolderLoadMore(getMailbox(uri));
                     return c;
@@ -1539,7 +1543,8 @@
         }
 
         if ((c != null) && !isTemporary()) {
-            c.setNotificationUri(getContext().getContentResolver(), uri);
+            // TODO(rtenneti): Enable notifications.
+            // c.setNotificationUri(getContext().getContentResolver(), uri);
         }
         return c;
     }
@@ -2546,7 +2551,8 @@
             final Set<Uri> notifications = getBatchNotificationsSet();
             setBatchNotificationsSet(null);
             for (final Uri uri : notifications) {
-                context.getContentResolver().notifyChange(uri, null);
+                // TODO(rtenneti): Enable notifications.
+                // context.getContentResolver().notifyChange(uri, null);
             }
         }
     }
@@ -2562,7 +2568,8 @@
         @Override
         public void attachmentChanged(final Context context, final long id, final int flags) {
             // The default implementation delegates to the real service.
-            AttachmentService.attachmentChanged(context, id, flags);
+            // TODO(rtenneti): Enable AttachmentService.
+            // AttachmentService.attachmentChanged(context, id, flags);
         }
     };
     private EmailAttachmentService mAttachmentService = DEFAULT_ATTACHMENT_SERVICE;
@@ -3976,7 +3983,8 @@
         } finally {
             accountIdCursor.close();
         }
-        mc.setNotificationUri(context.getContentResolver(), UIPROVIDER_ALL_ACCOUNTS_NOTIFIER);
+        // TODO(rtenneti): Enable notifications.
+        // mc.setNotificationUri(context.getContentResolver(), UIPROVIDER_ALL_ACCOUNTS_NOTIFIER);
 
         return mc;
     }
@@ -4637,8 +4645,9 @@
                     // Return real and virtual mailboxes alike
                     final Cursor rawc = db.rawQuery(genQueryAccountAllMailboxes(uiProjection),
                             new String[] {id});
-                    rawc.setNotificationUri(context.getContentResolver(), notifyUri);
-                    vc.setNotificationUri(context.getContentResolver(), notifyUri);
+                    // TODO(rtenneti): Enable notifications.
+                    // rawc.setNotificationUri(context.getContentResolver(), notifyUri);
+                    // vc.setNotificationUri(context.getContentResolver(), notifyUri);
                     if (rawc.getCount() > 0) {
                         c = new MergeCursor(new Cursor[]{rawc, vc});
                     } else {
@@ -4779,7 +4788,8 @@
                 break;
         }
         if (notifyUri != null) {
-            c.setNotificationUri(resolver, notifyUri);
+            // TODO(rtenneti): Enable notifications.
+            // c.setNotificationUri(resolver, notifyUri);
         }
         return c;
     }
@@ -5234,7 +5244,8 @@
             mailPrefs.setConversationOverviewMode(overviewMode);
         }
 
-        c.getContentResolver().notifyChange(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null, false);
+        // TODO(rtenneti): Enable notifications.
+        // c.getContentResolver().notifyChange(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null, false);
 
         return 1;
     }
@@ -5711,7 +5722,8 @@
         if (batchNotifications != null) {
             batchNotifications.add(notifyUri);
         } else {
-            getContext().getContentResolver().notifyChange(notifyUri, null);
+            // TODO(rtenneti): Enable notifications.
+            // getContext().getContentResolver().notifyChange(notifyUri, null);
         }
     }
 
@@ -6357,7 +6369,7 @@
         // Start/stop the various services depending on whether there are any accounts
         // TODO: Make sure that the AttachmentService responds to this request as it
         // expects a particular set of data in the intents that it receives or it ignores.
-        startOrStopService(enabled, context, new Intent(context, AttachmentService.class));
+        startOrStopService(enabled, context);
         final NotificationController controller =
                 NotificationControllerCreatorHolder.getInstance(context);
 
@@ -6367,16 +6379,16 @@
     }
 
     /**
-     * Starts or stops the service as necessary.
+     * Starts or stops the attachment service as necessary.
+     *
      * @param enabled If {@code true}, the service will be started. Otherwise, it will be stopped.
      * @param context The context to manage the service with.
-     * @param intent The intent of the service to be managed.
      */
-    private static void startOrStopService(boolean enabled, Context context, Intent intent) {
+    private static void startOrStopService(boolean enabled, Context context) {
         if (enabled) {
-            context.startService(intent);
+            AttachmentService.startWithoutSpecificAttachmentChange(context);
         } else {
-            context.stopService(intent);
+            AttachmentService.stop(context);
         }
     }
 
diff --git a/provider_src/com/android/email/service/AttachmentService.java b/provider_src/com/android/email/service/AttachmentService.java
index 6321049..1a628f7 100644
--- a/provider_src/com/android/email/service/AttachmentService.java
+++ b/provider_src/com/android/email/service/AttachmentService.java
@@ -27,15 +27,20 @@
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.Uri;
+import android.os.Build.VERSION_CODES;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.email.AttachmentInfo;
 import com.android.email.EmailConnectivityManager;
+import com.android.email.EmailNotificationController;
 import com.android.email.NotificationControllerCreatorHolder;
 import com.android.email.NotificationController;
+import com.android.email.R;
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.EmailContent.Attachment;
@@ -121,6 +126,10 @@
     // Signify that we are being shut down & destroyed.
     private volatile boolean mStop = false;
 
+    // Indicates whether this service is currently running. Currently, only used for Android O+ to
+    // decide whether to call startForegroundService or startService in start method.
+    private static volatile boolean isRunning = false;
+
     EmailConnectivityManager mConnectivityManager;
 
     // Helper class that keeps track of in progress downloads to make sure that they
@@ -586,7 +595,36 @@
         debugTrace("Calling startService with extras %d & %d", id, flags);
         intent.putExtra(EXTRA_ATTACHMENT_ID, id);
         intent.putExtra(EXTRA_ATTACHMENT_FLAGS, flags);
-        context.startService(intent);
+        start(context, intent);
+    }
+
+    public static void startWithoutSpecificAttachmentChange(Context context) {
+        LogUtils.d(LOG_TAG, "Going to start AttachmentService without specifying an attachment.");
+
+        Intent intent = new Intent(context, AttachmentService.class);
+        start(context, intent);
+    }
+
+    /**
+     * Starts running attachment service.
+     *
+     * @param intent an intent set to run AttachmentService class
+     */
+    public static void start(Context context, Intent intent) {
+        // TODO(rtenneti): Enable notifications.
+        // if (context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O &&
+        //        !isRunning) {
+        //    LogUtils.i(LOG_TAG, "startForegroundService");
+        //    context.startForegroundService(intent);
+        // } else {
+        //    LogUtils.i(LOG_TAG, "startService");
+        //    context.startService(intent);
+        // }
+    }
+
+    public static void stop(Context context) {
+        Intent intent = new Intent(context, AttachmentService.class);
+        context.stopService(intent);
     }
 
     /**
@@ -622,6 +660,16 @@
      */
     @Override
     public void onCreate() {
+        isRunning = true;
+        if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
+            LogUtils.i(LOG_TAG, "startForeground");
+            startForeground(
+                    EmailNotificationController.NOTIFICATION_ID_ONGOING_ATTACHMENT,
+                    EmailNotificationController.getOngoingDownloadNotification(
+                            getApplicationContext(),
+                            getApplicationContext().getString(
+                                    R.string.notification_downloading_attachments_title)));
+        }
         // Start up our service thread.
         new Thread(this, "AttachmentService").start();
     }
@@ -649,6 +697,7 @@
             mConnectivityManager.stopWait();
             mConnectivityManager = null;
         }
+        isRunning = false;
     }
 
     /**
diff --git a/provider_src/com/android/email/service/EmailBroadcastProcessorService.java b/provider_src/com/android/email/service/EmailBroadcastProcessorService.java
index 7aa5467..5d264f0 100644
--- a/provider_src/com/android/email/service/EmailBroadcastProcessorService.java
+++ b/provider_src/com/android/email/service/EmailBroadcastProcessorService.java
@@ -17,7 +17,6 @@
 package com.android.email.service;
 
 import android.accounts.AccountManager;
-import android.app.IntentService;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -34,6 +33,8 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 
+import androidx.core.app.JobIntentService;
+
 import com.android.email.EmailIntentService;
 import com.android.email.Preferences;
 import com.android.email.R;
@@ -69,7 +70,9 @@
  * This also handles the DeviceAdminReceiver in SecurityPolicy, because it is also
  * a BroadcastReceiver and requires the same processing semantics.
  */
-public class EmailBroadcastProcessorService extends IntentService {
+public class EmailBroadcastProcessorService extends JobIntentService {
+    public static final int JOB_ID = 200;
+
     // Action used for BroadcastReceiver entry point
     private static final String ACTION_BROADCAST = "broadcast_receiver";
 
@@ -81,11 +84,11 @@
     private static final String ACTION_UPGRADE_BROADCAST = "upgrade_broadcast_receiver";
 
     public EmailBroadcastProcessorService() {
-        // Class name will be the thread name.
-        super(EmailBroadcastProcessorService.class.getName());
+        super();
+    }
 
-        // Intent should be redelivered if the process gets killed before completing the job.
-        setIntentRedelivery(true);
+    public static void enqueueWork(Context context, Intent work) {
+        enqueueWork(context, EmailBroadcastProcessorService.class, JOB_ID, work);
     }
 
     /**
@@ -95,13 +98,13 @@
         Intent i = new Intent(context, EmailBroadcastProcessorService.class);
         i.setAction(ACTION_BROADCAST);
         i.putExtra(Intent.EXTRA_INTENT, broadcastIntent);
-        context.startService(i);
+        EmailBroadcastProcessorService.enqueueWork(context, i);
     }
 
     public static void processUpgradeBroadcastIntent(final Context context) {
         final Intent i = new Intent(context, EmailBroadcastProcessorService.class);
         i.setAction(ACTION_UPGRADE_BROADCAST);
-        context.startService(i);
+        EmailBroadcastProcessorService.enqueueWork(context, i);
     }
 
     /**
@@ -113,11 +116,11 @@
         Intent i = new Intent(context, EmailBroadcastProcessorService.class);
         i.setAction(ACTION_DEVICE_POLICY_ADMIN);
         i.putExtra(EXTRA_DEVICE_POLICY_ADMIN, message);
-        context.startService(i);
+        EmailBroadcastProcessorService.enqueueWork(context, i);
     }
 
     @Override
-    protected void onHandleIntent(Intent intent) {
+    protected void onHandleWork(Intent intent) {
         // This method is called on a worker thread.
 
         // Dispatch from entry point
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2f39425..dd1d5c1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -110,6 +110,9 @@
     <!-- Notification title when a forwarded attachment couldn't be sent [CHAR LIMIT=30]-->
     <string name="forward_download_failed_title">Attachment not forwarded</string>
 
+    <!-- Notification title when an attachment is being downloaded on Android O and later [CHAR LIMIT=30] -->
+    <string name="notification_downloading_attachments_title">Syncing mail&#8230;</string>
+
     <!-- Notification ticker when email account authentication fails [CHAR LIMIT=none] -->
     <string name="login_failed_ticker">
         <xliff:g id="account_name">%s</xliff:g> signin unsuccessful.</string>
diff --git a/src/com/android/email/EmailNotificationController.java b/src/com/android/email/EmailNotificationController.java
index 6773f1b..c9254ad 100644
--- a/src/com/android/email/EmailNotificationController.java
+++ b/src/com/android/email/EmailNotificationController.java
@@ -68,6 +68,10 @@
     private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
     private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
     private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
+    private static final int NOTIFICATION_ID_PERMISSIONS_NEEDED = 6;
+    public static final int NOTIFICATION_ID_ONGOING_ATTACHMENT = 7;
+
+    public static final String NOTIFICATION_CHANNEL_ID_ATTACHMENTS = "^nc_~_z_attachments";
 
     private static final int NOTIFICATION_ID_BASE_MASK = 0xF0000000;
     private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
@@ -401,6 +405,31 @@
     }
 
     /**
+     * Creates a notification to be used with {@link com.android.email.service.AttachmentService},
+     * which should be launched as a foreground service on Android O+.
+     *
+     * <p>The notification is sent with the lowest priority and contains an indefinite loading bar,
+     * hence "ongoing".
+     *
+     * @param title The text that will be displayed on the ongoing notification.
+     */
+    public static Notification getOngoingDownloadNotification(Context context, String title) {
+        NotificationCompat.Builder builder =
+                new NotificationCompat.Builder(context)
+                        .setContentTitle(title)
+                        .setVisibility(Notification.VISIBILITY_SECRET)
+                        .setProgress(0, 0, true)
+                        .setSmallIcon(R.drawable.ic_notification_mail_24dp)
+                        .setOngoing(true);
+
+        if (context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
+            builder.setChannelId(NOTIFICATION_CHANNEL_ID_ATTACHMENTS);
+        }
+
+        return builder.build();
+    }
+
+    /**
      * Returns a notification ID for login failed notifications for the given account account.
      */
     private static int getLoginFailedNotificationId(long accountId) {