Merge "Revert "Revert "AOSP/Email - Bump targetSdkVersion to 28."""
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9dd32c2..4fc82cb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -508,6 +508,7 @@
         <service
             android:name=".service.AttachmentService"
             android:enabled="false"
+            android:permission="android.permission.BIND_JOB_SERVICE"
             >
         </service>
 
diff --git a/provider_src/com/android/email/provider/EmailProvider.java b/provider_src/com/android/email/provider/EmailProvider.java
index 00d608f..1f0956b 100644
--- a/provider_src/com/android/email/provider/EmailProvider.java
+++ b/provider_src/com/android/email/provider/EmailProvider.java
@@ -6357,7 +6357,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 +6367,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..50ee429 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,35 @@
         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) {
+        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 +659,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 +696,7 @@
             mConnectivityManager.stopWait();
             mConnectivityManager = null;
         }
+        isRunning = false;
     }
 
     /**
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) {