//
// ExecutionService.java
//
// Copyright (c) 2017 Couchbase, Inc All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.couchbase.lite.internal;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;

import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;

import com.couchbase.lite.LogDomain;
import com.couchbase.lite.internal.support.Log;
import com.couchbase.lite.internal.utils.Preconditions;


/**
 * ExecutionService for Android.
 */
public final class AndroidExecutionService extends AbstractExecutionService {
    private static class CancellableTask implements Cancellable {
        private final Handler handler;
        private final Runnable task;

        private CancellableTask(@NonNull Handler handler, @NonNull Runnable task) {
            Preconditions.checkArgNotNull(handler, "handler");
            Preconditions.checkArgNotNull(task, "task");
            this.handler = handler;
            this.task = task;
        }

        @Override
        public void cancel() { handler.removeCallbacks(task); }
    }


    private final Handler mainHandler;
    private final Executor mainThreadExecutor;

    public AndroidExecutionService() {
        mainHandler = new Handler(Looper.getMainLooper());
        mainThreadExecutor = mainHandler::post;
    }

    @NonNull
    @Override
    public Executor getThreadPoolExecutor() { return AsyncTask.THREAD_POOL_EXECUTOR; }

    @NonNull
    @Override
    public Executor getMainExecutor() { return mainThreadExecutor; }

    /**
     * This runs a task on Android's main thread for just long enough to enough to enqueue the passed task
     * on the passed executor.  It occupies space in the main looper's message queue
     * but takes no significant time processing, there.
     * If the target executor refuses the task, it is just dropped on the floor.
     *
     * @param delayMs  delay before posting the task.  There may be additional queue delays in the executor.
     * @param executor an executor on which to execute the task.
     * @param task     the task to be executed.
     * @return a cancellable task
     */
    @NonNull
    @Override
    public Cancellable postDelayedOnExecutor(long delayMs, @NonNull Executor executor, @NonNull Runnable task) {
        Preconditions.checkArgNotNull(executor, "executor");
        Preconditions.checkArgNotNull(task, "task");
        final Runnable delayedTask = () -> {
            try { executor.execute(task); }
            catch (CloseableExecutor.ExecutorClosedExeception e) {
                Log.w(LogDomain.DATABASE, "Scheduled on closed executor: " + task + ", " + executor);
            }
            catch (RejectedExecutionException e) {
                throw new IllegalStateException("Execution rejected in status change: notification: " + executor, e);
            }
        };
        mainHandler.postDelayed(delayedTask, delayMs);
        return new CancellableTask(mainHandler, delayedTask);
    }

    /**
     * Best effort, delete the passed task (obtained from postDelayedOnExecutor, above)
     * from the wait queue.  If it is already in the Executor, well, there you go.
     *
     * @param cancellableTask returned by a previous call to postDelayedOnExecutor.
     */
    @Override
    public void cancelDelayedTask(@NonNull Cancellable cancellableTask) {
        Preconditions.checkArgNotNull(cancellableTask, "cancellableTask");
        cancellableTask.cancel();
    }
}

@@ -801,16 +801,69 @@
 NonNull;
+%0Aimport android.support.annotation.VisibleForTesting;
 %0A%0Aimport
@@ -949,16 +949,64 @@
 ception;
+%0Aimport java.util.concurrent.ThreadPoolExecutor;
 %0A%0Aimport
@@ -1904,234 +1904,305 @@
 () %7B
-%0A        mainHandler = new Handler(Looper.getMainLooper());%0A        mainThreadExecutor = mainHandler::post;%0A    %7D%0A%0A    @NonNull%0A    @Override%0A    public Executor getThreadPoolExecutor() %7B return AsyncTask.THREAD_POOL_EXECUTOR;
+ this((ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR); %7D%0A%0A    @VisibleForTesting%0A    AndroidExecutionService(@NonNull ThreadPoolExecutor baseExecutor) %7B%0A        super(baseExecutor);%0A        mainHandler = new Handler(Looper.getMainLooper());%0A        mainThreadExecutor = mainHandler::post;%0A   
  %7D%0A%0A
@@ -3152,32 +3152,33 @@
 (task, %22task%22);%0A
+%0A
         final Ru
@@ -3504,107 +3504,57 @@
     
-throw new IllegalStateException(%22Execution rejected in status change: notification: %22 + executor, e
+dumpServiceState(executor, e, %22after: %22 + delayMs
 );%0A 
@@ -3577,16 +3577,17 @@
      %7D;%0A
+%0A
         
@@ -3625,32 +3625,33 @@
 Task, delayMs);%0A
+%0A
         return n

Add thread diagnostics (#1938)