Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2078e440 authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Merge cherrypicks of [6841458, 6845109, 6844203, 6845110, 6845111, 6845112,...

Merge cherrypicks of [6841458, 6845109, 6844203, 6845110, 6845111, 6845112, 6845113, 6845114, 6845115, 6845116, 6845117, 6845118, 6845119, 6845376, 6845444, 6845377, 6844204, 6844205, 6844206, 6844207, 6844208, 6844209, 6844210, 6844211, 6844212, 6844213] into pi-qpr3-b-release

Change-Id: I6f80b69a1172ff84d9cd1a9c23bf3ee806c489ca
parents dc22f0d6 8e55cdca
Loading
Loading
Loading
Loading
+49 −35
Original line number Original line Diff line number Diff line
@@ -37,13 +37,12 @@ import com.android.documentsui.base.Features;
import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;


import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.GuardedBy;


@@ -99,9 +98,9 @@ public class FileOperationService extends Service implements Job.Listener {


    private static final int POOL_SIZE = 2;  // "pool size", not *max* "pool size".
    private static final int POOL_SIZE = 2;  // "pool size", not *max* "pool size".


    private static final int NOTIFICATION_ID_PROGRESS = 0;
    @VisibleForTesting static final int NOTIFICATION_ID_PROGRESS = 1;
    private static final int NOTIFICATION_ID_FAILURE = 1;
    private static final int NOTIFICATION_ID_FAILURE = 2;
    private static final int NOTIFICATION_ID_WARNING = 2;
    private static final int NOTIFICATION_ID_WARNING = 3;


    // The executor and job factory are visible for testing and non-final
    // The executor and job factory are visible for testing and non-final
    // so we'll have a way to inject test doubles from the test. It's
    // so we'll have a way to inject test doubles from the test. It's
@@ -124,10 +123,11 @@ public class FileOperationService extends Service implements Job.Listener {
    @VisibleForTesting Features features;
    @VisibleForTesting Features features;


    @GuardedBy("mJobs")
    @GuardedBy("mJobs")
    private final Map<String, JobRecord> mJobs = new HashMap<>();
    private final Map<String, JobRecord> mJobs = new LinkedHashMap<>();


    // The job whose notification is used to keep the service in foreground mode.
    // The job whose notification is used to keep the service in foreground mode.
    private final AtomicReference<Job> mForegroundJob = new AtomicReference<>();
    @GuardedBy("mJobs")
    private Job mForegroundJob;


    private PowerManager mPowerManager;
    private PowerManager mPowerManager;
    private PowerManager.WakeLock mWakeLock;  // the wake lock, if held.
    private PowerManager.WakeLock mWakeLock;  // the wake lock, if held.
@@ -270,6 +270,7 @@ public class FileOperationService extends Service implements Job.Listener {
            JobRecord record = mJobs.get(jobId);
            JobRecord record = mJobs.get(jobId);
            if (record != null) {
            if (record != null) {
                record.job.cancel();
                record.job.cancel();
                updateForegroundState(record.job);
            }
            }
        }
        }


@@ -343,18 +344,23 @@ public class FileOperationService extends Service implements Job.Listener {


        Notification notification = job.getSetupNotification();
        Notification notification = job.getSetupNotification();
        // If there is no foreground job yet, set this job to foreground job.
        // If there is no foreground job yet, set this job to foreground job.
        if (mForegroundJob.compareAndSet(null, job)) {
        synchronized (mJobs) {
            if (mForegroundJob == null) {
                if (DEBUG) Log.d(TAG, "Set foreground job to " + job.id);
                if (DEBUG) Log.d(TAG, "Set foreground job to " + job.id);
                mForegroundJob = job;
                foregroundManager.startForeground(NOTIFICATION_ID_PROGRESS, notification);
                foregroundManager.startForeground(NOTIFICATION_ID_PROGRESS, notification);
        }
            } else {

                // Show start up notification
                // Show start up notification
                if (DEBUG) Log.d(TAG, "Posting notification for " + job.id);
                if (DEBUG) Log.d(TAG, "Posting notification for " + job.id);
                notificationManager.notify(
                notificationManager.notify(
                job.id, NOTIFICATION_ID_PROGRESS, notification);
                        mForegroundJob == job ? null : job.id,
                        NOTIFICATION_ID_PROGRESS,
                        notification);
            }
        }


        // Set up related monitor
        // Set up related monitor
        JobMonitor monitor = new JobMonitor(job, notificationManager, handler, mJobs);
        JobMonitor monitor = new JobMonitor(job);
        monitor.start();
        monitor.start();
    }
    }


@@ -388,11 +394,12 @@ public class FileOperationService extends Service implements Job.Listener {


    @GuardedBy("mJobs")
    @GuardedBy("mJobs")
    private void updateForegroundState(Job job) {
    private void updateForegroundState(Job job) {
        Job candidate = mJobs.isEmpty() ? null : mJobs.values().iterator().next().job;
        Job candidate = getCandidateForegroundJob();


        // If foreground job is retiring and there is still work to do, we need to set it to a new
        // If foreground job is retiring and there is still work to do, we need to set it to a new
        // job.
        // job.
        if (mForegroundJob.compareAndSet(job, candidate)) {
        if (mForegroundJob == job) {
            mForegroundJob = candidate;
            if (candidate == null) {
            if (candidate == null) {
                if (DEBUG) Log.d(TAG, "Stop foreground");
                if (DEBUG) Log.d(TAG, "Stop foreground");
                // Remove the notification here just in case we're torn down before we have the
                // Remove the notification here just in case we're torn down before we have the
@@ -401,12 +408,11 @@ public class FileOperationService extends Service implements Job.Listener {
            } else {
            } else {
                if (DEBUG) Log.d(TAG, "Switch foreground job to " + candidate.id);
                if (DEBUG) Log.d(TAG, "Switch foreground job to " + candidate.id);


                notificationManager.cancel(candidate.id, NOTIFICATION_ID_PROGRESS);
                Notification notification = (candidate.getState() == Job.STATE_STARTED)
                Notification notification = (candidate.getState() == Job.STATE_STARTED)
                        ? candidate.getSetupNotification()
                        ? candidate.getSetupNotification()
                        : candidate.getProgressNotification();
                        : candidate.getProgressNotification();
                foregroundManager.startForeground(NOTIFICATION_ID_PROGRESS, notification);
                notificationManager.notify(NOTIFICATION_ID_PROGRESS, notification);
                notificationManager.notify(candidate.id, NOTIFICATION_ID_PROGRESS,
                        notification);
            }
            }
        }
        }
    }
    }
@@ -435,6 +441,19 @@ public class FileOperationService extends Service implements Job.Listener {
        }
        }
    }
    }


    @GuardedBy("mJobs")
    private Job getCandidateForegroundJob() {
        if (mJobs.isEmpty()) {
            return null;
        }
        for (JobRecord rec : mJobs.values()) {
            if (!rec.job.isFinished()) {
                return rec.job;
            }
        }
        return null;
    }

    private static final class JobRecord {
    private static final class JobRecord {
        private final Job job;
        private final Job job;
        private final Future<?> future;
        private final Future<?> future;
@@ -452,29 +471,22 @@ public class FileOperationService extends Service implements Job.Listener {
     * still need to update notifications if jobs hang, so instead of jobs pushing their states,
     * still need to update notifications if jobs hang, so instead of jobs pushing their states,
     * we poll states of jobs.
     * we poll states of jobs.
     */
     */
    private static final class JobMonitor implements Runnable {
    private final class JobMonitor implements Runnable {
        private static final long PROGRESS_INTERVAL_MILLIS = 500L;
        private static final long PROGRESS_INTERVAL_MILLIS = 500L;


        private final Job mJob;
        private final Job mJob;
        private final NotificationManager mNotificationManager;
        private final Handler mHandler;
        private final Object mJobsLock;


        private JobMonitor(Job job, NotificationManager notificationManager, Handler handler,
        private JobMonitor(Job job) {
                Object jobsLock) {
            mJob = job;
            mJob = job;
            mNotificationManager = notificationManager;
            mHandler = handler;
            mJobsLock = jobsLock;
        }
        }


        private void start() {
        private void start() {
            mHandler.post(this);
            handler.post(this);
        }
        }


        @Override
        @Override
        public void run() {
        public void run() {
            synchronized (mJobsLock) {
            synchronized (mJobs) {
                if (mJob.isFinished()) {
                if (mJob.isFinished()) {
                    // Finish notification is already shown. Progress notification is removed.
                    // Finish notification is already shown. Progress notification is removed.
                    // Just finish itself.
                    // Just finish itself.
@@ -483,11 +495,13 @@ public class FileOperationService extends Service implements Job.Listener {


                // Only job in set up state has progress bar
                // Only job in set up state has progress bar
                if (mJob.getState() == Job.STATE_SET_UP) {
                if (mJob.getState() == Job.STATE_SET_UP) {
                    mNotificationManager.notify(
                    notificationManager.notify(
                            mJob.id, NOTIFICATION_ID_PROGRESS, mJob.getProgressNotification());
                            mForegroundJob == mJob ? null : mJob.id,
                            NOTIFICATION_ID_PROGRESS,
                            mJob.getProgressNotification());
                }
                }


                mHandler.postDelayed(this, PROGRESS_INTERVAL_MILLIS);
                handler.postDelayed(this, PROGRESS_INTERVAL_MILLIS);
            }
            }
        }
        }
    }
    }
+8 −8
Original line number Original line Diff line number Diff line
@@ -23,17 +23,25 @@ import android.app.Notification;


class TestForegroundManager implements FileOperationService.ForegroundManager {
class TestForegroundManager implements FileOperationService.ForegroundManager {


    private final TestNotificationManager mNotificationManager;
    private int mForegroundId = -1;
    private int mForegroundId = -1;
    private Notification mForegroundNotification;
    private Notification mForegroundNotification;


    TestForegroundManager(TestNotificationManager notificationManager) {
        assert(notificationManager != null);
        mNotificationManager = notificationManager;
    }

    @Override
    @Override
    public void startForeground(int id, Notification notification) {
    public void startForeground(int id, Notification notification) {
        mForegroundId = id;
        mForegroundId = id;
        mForegroundNotification = notification;
        mForegroundNotification = notification;
        mNotificationManager.notify(null, mForegroundId, mForegroundNotification);
    }
    }


    @Override
    @Override
    public void stopForeground(boolean cancelNotification) {
    public void stopForeground(boolean cancelNotification) {
        mNotificationManager.cancel(null, mForegroundId);
        mForegroundId = -1;
        mForegroundId = -1;
        mForegroundNotification = null;
        mForegroundNotification = null;
    }
    }
@@ -45,12 +53,4 @@ class TestForegroundManager implements FileOperationService.ForegroundManager {
    void assertInBackground() {
    void assertInBackground() {
        assertNull(mForegroundNotification);
        assertNull(mForegroundNotification);
    }
    }

    int getForegroundId() {
        return mForegroundId;
    }

    Notification getForegroundNotification() {
        return mForegroundNotification;
    }
}
}
+20 −18
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.documentsui.services;
package com.android.documentsui.services;


import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.assertTrue;


@@ -32,21 +33,10 @@ import java.util.HashMap;


class TestNotificationManager {
class TestNotificationManager {


    private final TestForegroundManager mForegroundManager;
    private final SparseArray<HashMap<String, Notification>> mNotifications = new SparseArray<>();
    private final SparseArray<HashMap<String, Notification>> mNotifications = new SparseArray<>();
    private final Answer<Void> mAnswer = this::invoke;
    private final Answer<Void> mAnswer = this::invoke;


    TestNotificationManager(TestForegroundManager foregroundManager) {
    void notify(String tag, int id, Notification notification) {
        assert(foregroundManager != null);
        mForegroundManager = foregroundManager;
    }

    private void notify(String tag, int id, Notification notification) {
        if (notification == mForegroundManager.getForegroundNotification()
                && id != mForegroundManager.getForegroundId()) {
            throw new IllegalStateException("Mismatching ID and notification.");
        }

        if (mNotifications.get(id) == null) {
        if (mNotifications.get(id) == null) {
            mNotifications.put(id, new HashMap<>());
            mNotifications.put(id, new HashMap<>());
        }
        }
@@ -54,16 +44,12 @@ class TestNotificationManager {
        mNotifications.get(id).put(tag, notification);
        mNotifications.get(id).put(tag, notification);
    }
    }


    private void cancel(String tag, int id) {
    void cancel(String tag, int id) {
        final HashMap<String, Notification> idMap = mNotifications.get(id);
        final HashMap<String, Notification> idMap = mNotifications.get(id);
        if (idMap != null && idMap.containsKey(tag)) {
        if (idMap != null && idMap.containsKey(tag)) {
            final Notification notification = idMap.get(tag);
            // Only cancel non-foreground notification
            if (mForegroundManager.getForegroundNotification() != notification) {
            idMap.remove(tag);
            idMap.remove(tag);
        }
        }
    }
    }
    }


    private Void invoke(InvocationOnMock invocation) {
    private Void invoke(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        Object[] args = invocation.getArguments();
@@ -88,6 +74,14 @@ class TestNotificationManager {
        return null;
        return null;
    }
    }


    private boolean hasNotification(int id, String jobId) {
        if (mNotifications.get(id) == null) {
            return false;
        }
        Notification notification = mNotifications.get(id).get(jobId);
        return notification != null;
    }

    NotificationManager createNotificationManager() {
    NotificationManager createNotificationManager() {
        return Mockito.mock(NotificationManager.class, mAnswer);
        return Mockito.mock(NotificationManager.class, mAnswer);
    }
    }
@@ -100,4 +94,12 @@ class TestNotificationManager {


        assertEquals(expect, count);
        assertEquals(expect, count);
    }
    }

    void assertHasNotification(int id, String jobid) {
        assertTrue(hasNotification(id, jobid));
    }

    void assertNoNotification(int id, String jobid) {
        assertFalse(hasNotification(id, jobid));
    }
}
}
+28 −2
Original line number Original line Diff line number Diff line
@@ -78,8 +78,8 @@ public class FileOperationServiceTest extends ServiceTestCase<FileOperationServi
        mExecutor = new TestScheduledExecutorService();
        mExecutor = new TestScheduledExecutorService();
        mDeletionExecutor = new TestScheduledExecutorService();
        mDeletionExecutor = new TestScheduledExecutorService();
        mHandler = new TestHandler();
        mHandler = new TestHandler();
        mForegroundManager = new TestForegroundManager();
        mTestNotificationManager = new TestNotificationManager();
        mTestNotificationManager = new TestNotificationManager(mForegroundManager);
        mForegroundManager = new TestForegroundManager(mTestNotificationManager);
        TestFeatures features = new TestFeatures();
        TestFeatures features = new TestFeatures();
        features.notificationChannel = InstrumentationRegistry.getTargetContext()
        features.notificationChannel = InstrumentationRegistry.getTargetContext()
                .getResources().getBoolean(R.bool.feature_notification_channel);
                .getResources().getBoolean(R.bool.feature_notification_channel);
@@ -294,6 +294,32 @@ public class FileOperationServiceTest extends ServiceTestCase<FileOperationServi
        mTestNotificationManager.assertNumberOfNotifications(0);
        mTestNotificationManager.assertNumberOfNotifications(0);
    }
    }


    public void testNotificationUpdateAfterForegroundJobSwitch() throws Exception {
        startService(createCopyIntent(newArrayList(ALPHA_DOC), BETA_DOC));
        startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC));
        Job job1 = mCopyJobs.get(0);
        Job job2 = mCopyJobs.get(1);

        mService.onStart(job1);
        mTestNotificationManager.assertHasNotification(
                FileOperationService.NOTIFICATION_ID_PROGRESS, null);

        mService.onStart(job2);
        mTestNotificationManager.assertHasNotification(
                FileOperationService.NOTIFICATION_ID_PROGRESS, job2.id);

        job1.cancel();
        mService.onFinished(job1);
        mTestNotificationManager.assertHasNotification(
                FileOperationService.NOTIFICATION_ID_PROGRESS, null);
        mTestNotificationManager.assertNoNotification(
                FileOperationService.NOTIFICATION_ID_PROGRESS, job2.id);

        job2.cancel();
        mService.onFinished(job2);
        mTestNotificationManager.assertNumberOfNotifications(0);
    }

    private Intent createCopyIntent(ArrayList<DocumentInfo> files, DocumentInfo dest)
    private Intent createCopyIntent(ArrayList<DocumentInfo> files, DocumentInfo dest)
            throws Exception {
            throws Exception {
        DocumentStack stack = new DocumentStack();
        DocumentStack stack = new DocumentStack();