1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.latin.utils;
18 
19 import android.util.Log;
20 
21 import com.android.inputmethod.annotations.UsedForTesting;
22 
23 import java.lang.Thread.UncaughtExceptionHandler;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.ThreadFactory;
27 import java.util.concurrent.TimeUnit;
28 
29 /**
30  * Utilities to manage executors.
31  */
32 public class ExecutorUtils {
33 
34     private static final String TAG = "ExecutorUtils";
35 
36     public static final String KEYBOARD = "Keyboard";
37     public static final String SPELLING = "Spelling";
38 
39     private static ScheduledExecutorService sKeyboardExecutorService = newExecutorService(KEYBOARD);
40     private static ScheduledExecutorService sSpellingExecutorService = newExecutorService(SPELLING);
41 
newExecutorService(final String name)42     private static ScheduledExecutorService newExecutorService(final String name) {
43         return Executors.newSingleThreadScheduledExecutor(new ExecutorFactory(name));
44     }
45 
46     private static class ExecutorFactory implements ThreadFactory {
47         private final String mName;
48 
ExecutorFactory(final String name)49         private ExecutorFactory(final String name) {
50             mName = name;
51         }
52 
53         @Override
newThread(final Runnable runnable)54         public Thread newThread(final Runnable runnable) {
55             Thread thread = new Thread(runnable, TAG);
56             thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
57                 @Override
58                 public void uncaughtException(Thread thread, Throwable ex) {
59                     Log.w(mName + "-" + runnable.getClass().getSimpleName(), ex);
60                 }
61             });
62             return thread;
63         }
64     }
65 
66     @UsedForTesting
67     private static ScheduledExecutorService sExecutorServiceForTests;
68 
69     @UsedForTesting
setExecutorServiceForTests( final ScheduledExecutorService executorServiceForTests)70     public static void setExecutorServiceForTests(
71             final ScheduledExecutorService executorServiceForTests) {
72         sExecutorServiceForTests = executorServiceForTests;
73     }
74 
75     //
76     // Public methods used to schedule a runnable for execution.
77     //
78 
79     /**
80      * @param name Executor's name.
81      * @return scheduled executor service used to run background tasks
82      */
getBackgroundExecutor(final String name)83     public static ScheduledExecutorService getBackgroundExecutor(final String name) {
84         if (sExecutorServiceForTests != null) {
85             return sExecutorServiceForTests;
86         }
87         switch (name) {
88             case KEYBOARD:
89                 return sKeyboardExecutorService;
90             case SPELLING:
91                 return sSpellingExecutorService;
92             default:
93                 throw new IllegalArgumentException("Invalid executor: " + name);
94         }
95     }
96 
killTasks(final String name)97     public static void killTasks(final String name) {
98         final ScheduledExecutorService executorService = getBackgroundExecutor(name);
99         executorService.shutdownNow();
100         try {
101             executorService.awaitTermination(5, TimeUnit.SECONDS);
102         } catch (InterruptedException e) {
103             Log.wtf(TAG, "Failed to shut down: " + name);
104         }
105         if (executorService == sExecutorServiceForTests) {
106             // Don't do anything to the test service.
107             return;
108         }
109         switch (name) {
110             case KEYBOARD:
111                 sKeyboardExecutorService = newExecutorService(KEYBOARD);
112                 break;
113             case SPELLING:
114                 sSpellingExecutorService = newExecutorService(SPELLING);
115                 break;
116             default:
117                 throw new IllegalArgumentException("Invalid executor: " + name);
118         }
119     }
120 
121     @UsedForTesting
chain(final Runnable... runnables)122     public static Runnable chain(final Runnable... runnables) {
123         return new RunnableChain(runnables);
124     }
125 
126     @UsedForTesting
127     public static class RunnableChain implements Runnable {
128         private final Runnable[] mRunnables;
129 
RunnableChain(final Runnable... runnables)130         private RunnableChain(final Runnable... runnables) {
131             if (runnables == null || runnables.length == 0) {
132                 throw new IllegalArgumentException("Attempting to construct an empty chain");
133             }
134             mRunnables = runnables;
135         }
136 
137         @UsedForTesting
getRunnables()138         public Runnable[] getRunnables() {
139             return mRunnables;
140         }
141 
142         @Override
run()143         public void run() {
144             for (Runnable runnable : mRunnables) {
145                 if (Thread.interrupted()) {
146                     return;
147                 }
148                 runnable.run();
149             }
150         }
151     }
152 }
153