[go: nahoru, domu]

blob: 31068655a869c0778bc88593c03046399ff7bdab [file] [log] [blame]
Jake Wharton2d521652018-03-21 11:53:41 -04001/*
2 * Copyright 2018 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
17package androidx.core.os;
18
Aurimas Liutikasdb916992018-06-06 17:04:06 -070019import android.os.Build;
Jake Wharton2d521652018-03-21 11:53:41 -040020import android.os.Handler;
Jake Wharton1af4b8d2018-07-18 15:31:39 -040021import android.os.Looper;
Jake Wharton2d521652018-03-21 11:53:41 -040022import android.os.Message;
Jake Wharton1af4b8d2018-07-18 15:31:39 -040023import android.util.Log;
Jake Wharton2d521652018-03-21 11:53:41 -040024
25import androidx.annotation.NonNull;
26import androidx.annotation.Nullable;
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050027import androidx.annotation.RequiresApi;
Jake Wharton2d521652018-03-21 11:53:41 -040028
Jake Wharton1af4b8d2018-07-18 15:31:39 -040029import java.lang.reflect.InvocationTargetException;
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050030import java.lang.reflect.Method;
Jake Wharton1af4b8d2018-07-18 15:31:39 -040031
Jake Wharton2d521652018-03-21 11:53:41 -040032/**
33 * Helper for accessing features in {@link Handler}.
34 */
35public final class HandlerCompat {
Jake Wharton1af4b8d2018-07-18 15:31:39 -040036 private static final String TAG = "HandlerCompat";
37
38 /**
39 * Create a new Handler whose posted messages and runnables are not subject to
40 * synchronization barriers such as display vsync.
41 *
42 * <p>Messages sent to an async handler are guaranteed to be ordered with respect to one
43 * another, but not necessarily with respect to messages from other Handlers.</p>
44 *
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050045 * @see Handler#createAsync(Looper, Handler.Callback) to create an async Handler with custom
46 * message handling.
47 *
48 * Compatibility behavior:
49 * <ul>
50 * <li>SDK 28 and above, this method matches platform behavior.
51 * <li>SDK 17 through 27, this method attempts to call the platform API via reflection, but
52 * may fail and return a synchronous handler instance.
53 * <li>Below SDK 17, this method will always return a synchronous handler instance.
54 * </ul>
Jake Wharton1af4b8d2018-07-18 15:31:39 -040055 *
56 * @param looper the Looper that the new Handler should be bound to
57 * @return a new async Handler instance
58 * @see Handler#createAsync(Looper)
59 */
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050060 @SuppressWarnings("JavaReflectionMemberAccess")
Jake Wharton1af4b8d2018-07-18 15:31:39 -040061 @NonNull
62 public static Handler createAsync(@NonNull Looper looper) {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050063 Exception wrappedException;
64
Jake Wharton1af4b8d2018-07-18 15:31:39 -040065 if (Build.VERSION.SDK_INT >= 28) {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050066 return Api28Impl.createAsync(looper);
67 } else if (Build.VERSION.SDK_INT >= 17) {
Jake Wharton1af4b8d2018-07-18 15:31:39 -040068 try {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050069 // This constructor was added as private in JB MR1:
70 // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr1-release/core/java/android/os/Handler.java
Dustin Lam47ce1ab2020-10-26 21:14:57 -070071 return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,
Jake Wharton1af4b8d2018-07-18 15:31:39 -040072 boolean.class)
73 .newInstance(looper, null, true);
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050074 } catch (IllegalAccessException e) {
75 wrappedException = e;
76 } catch (InstantiationException e) {
77 wrappedException = e;
78 } catch (NoSuchMethodException e) {
79 wrappedException = e;
Jake Wharton1af4b8d2018-07-18 15:31:39 -040080 } catch (InvocationTargetException e) {
81 Throwable cause = e.getCause();
82 if (cause instanceof RuntimeException) {
83 throw ((RuntimeException) cause);
84 }
85 if (cause instanceof Error) {
86 throw ((Error) cause);
87 }
88 throw new RuntimeException(cause);
89 }
Alan Viverette7e9c4bf2021-02-08 17:41:41 -050090 // This is a non-fatal failure, but it affects behavior and may be relevant when
91 // investigating issue reports.
92 Log.w(TAG, "Unable to invoke Handler(Looper, Callback, boolean) constructor",
93 wrappedException);
Jake Wharton1af4b8d2018-07-18 15:31:39 -040094 }
95 return new Handler(looper);
96 }
97
98 /**
99 * Create a new Handler whose posted messages and runnables are not subject to
100 * synchronization barriers such as display vsync.
101 *
102 * <p>Messages sent to an async handler are guaranteed to be ordered with respect to one
103 * another, but not necessarily with respect to messages from other Handlers.</p>
104 *
105 * @see #createAsync(Looper) to create an async Handler without custom message handling.
106 *
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500107 * Compatibility behavior:
108 * <ul>
109 * <li>SDK 28 and above, this method matches platform behavior.
110 * <li>SDK 17 through 27, this method attempts to call the platform API via reflection, but
111 * may fail and return a synchronous handler instance.
112 * <li>Below SDK 17, this method will always return a synchronous handler instance.
113 * </ul>
114 *
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400115 * @param looper the Looper that the new Handler should be bound to
Aurimas Liutikasc7098ea2023-08-18 15:45:46 -0700116 * @param callback callback to send events to
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400117 * @return a new async Handler instance
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500118 * @see Handler#createAsync(Looper, Handler.Callback)
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400119 */
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500120 @SuppressWarnings("JavaReflectionMemberAccess")
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400121 @NonNull
Dustin Lam47ce1ab2020-10-26 21:14:57 -0700122 public static Handler createAsync(@NonNull Looper looper, @NonNull Handler.Callback callback) {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500123 Exception wrappedException;
124
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400125 if (Build.VERSION.SDK_INT >= 28) {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500126 return Api28Impl.createAsync(looper, callback);
127 } else if (Build.VERSION.SDK_INT >= 17) {
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400128 try {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500129 // This constructor was added as private API in JB MR1:
130 // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr1-release/core/java/android/os/Handler.java
Dustin Lam47ce1ab2020-10-26 21:14:57 -0700131 return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400132 boolean.class)
133 .newInstance(looper, callback, true);
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500134 } catch (IllegalAccessException e) {
135 wrappedException = e;
136 } catch (InstantiationException e) {
137 wrappedException = e;
138 } catch (NoSuchMethodException e) {
139 wrappedException = e;
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400140 } catch (InvocationTargetException e) {
141 Throwable cause = e.getCause();
142 if (cause instanceof RuntimeException) {
143 throw ((RuntimeException) cause);
144 }
145 if (cause instanceof Error) {
146 throw ((Error) cause);
147 }
148 throw new RuntimeException(cause);
149 }
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500150 // This is a non-fatal failure, but it affects behavior and may be relevant when
151 // investigating issue reports.
152 Log.w(TAG, "Unable to invoke Handler(Looper, Callback, boolean) constructor",
153 wrappedException);
Jake Wharton1af4b8d2018-07-18 15:31:39 -0400154 }
155 return new Handler(looper, callback);
156 }
157
Jake Wharton2d521652018-03-21 11:53:41 -0400158 /**
159 * Causes the Runnable r to be added to the message queue, to be run
160 * after the specified amount of time elapses.
161 * The runnable will be run on the thread to which this handler
162 * is attached.
163 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
164 * Time spent in deep sleep will add an additional delay to execution.
165 *
Aurimas Liutikasc7098ea2023-08-18 15:45:46 -0700166 * @param handler handler to use for posting the runnable.
Jake Wharton2d521652018-03-21 11:53:41 -0400167 * @param r The Runnable that will be executed.
168 * @param token An instance which can be used to cancel {@code r} via
169 * {@link Handler#removeCallbacksAndMessages}.
170 * @param delayMillis The delay (in milliseconds) until the Runnable
171 * will be executed.
172 *
173 * @return Returns true if the Runnable was successfully placed in to the
174 * message queue. Returns false on failure, usually because the
175 * looper processing the message queue is exiting. Note that a
176 * result of true does not mean the Runnable will be processed --
177 * if the looper is quit before the delivery time of the message
178 * occurs then the message will be dropped.
179 *
180 * @see Handler#postDelayed(Runnable, Object, long)
181 */
182 public static boolean postDelayed(@NonNull Handler handler, @NonNull Runnable r,
183 @Nullable Object token, long delayMillis) {
Aurimas Liutikasdb916992018-06-06 17:04:06 -0700184 if (Build.VERSION.SDK_INT >= 28) {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500185 return Api28Impl.postDelayed(handler, r, token, delayMillis);
Jake Wharton2d521652018-03-21 11:53:41 -0400186 }
187
188 Message message = Message.obtain(handler, r);
189 message.obj = token;
190 return handler.sendMessageDelayed(message, delayMillis);
191 }
192
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500193 /**
194 * Checks if there are any pending posts of messages with callback {@code r} in
195 * the message queue.
196 *
197 * Compatibility behavior:
198 * <ul>
199 * <li>SDK 29 and above, this method matches platform behavior.
200 * <li>SDK 16 through 28, this method attempts to call the platform API via reflection, but
201 * will throw an unchecked exception if the method has been altered from the AOSP
202 * implementation and cannot be called. This is unlikely, but there is no safe fallback case
203 * for this method and we must throw an exception as a result.
204 * </ul>
205 *
206 * @param handler handler on which to call the method
207 * @param r callback to look for in the message queue
208 * @return {@code true} if the callback is in the message queue
209 * @see Handler#hasCallbacks(Runnable)
210 */
211 @RequiresApi(16)
212 public static boolean hasCallbacks(@NonNull Handler handler, @NonNull Runnable r) {
213 Exception wrappedException = null;
214
215 if (Build.VERSION.SDK_INT >= 29) {
216 return Api29Impl.hasCallbacks(handler, r);
217 } else if (Build.VERSION.SDK_INT >= 16) {
218 // The method signature didn't change when it was made public in SDK 29, but use
219 // reflection so that we don't cause a verification error or NotFound exception if an
220 // OEM changed something.
221 try {
222 Method hasCallbacksMethod = Handler.class.getMethod("hasCallbacks", Runnable.class);
223 //noinspection ConstantConditions
224 return (boolean) hasCallbacksMethod.invoke(handler, r);
225 } catch (InvocationTargetException e) {
226 Throwable cause = e.getCause();
227 if (cause instanceof RuntimeException) {
228 throw ((RuntimeException) cause);
229 }
230 if (cause instanceof Error) {
231 throw ((Error) cause);
232 }
233 throw new RuntimeException(cause);
234 } catch (IllegalAccessException e) {
235 wrappedException = e;
236 } catch (NoSuchMethodException e) {
237 wrappedException = e;
238 } catch (NullPointerException e) {
239 wrappedException = e;
240 }
241 }
242
243 throw new UnsupportedOperationException("Failed to call Handler.hasCallbacks(), but there"
244 + " is no safe failure mode for this method. Raising exception.", wrappedException);
245 }
246
Jake Wharton2d521652018-03-21 11:53:41 -0400247 private HandlerCompat() {
Alan Viverette7e9c4bf2021-02-08 17:41:41 -0500248 // Non-instantiable.
249 }
250
251 @RequiresApi(29)
252 private static class Api29Impl {
253 private Api29Impl() {
254 // Non-instantiable.
255 }
256
257 public static boolean hasCallbacks(Handler handler, Runnable r) {
258 return handler.hasCallbacks(r);
259 }
260 }
261
262 @RequiresApi(28)
263 private static class Api28Impl {
264 private Api28Impl() {
265 // Non-instantiable.
266 }
267
268 public static Handler createAsync(Looper looper) {
269 return Handler.createAsync(looper);
270 }
271
272 public static Handler createAsync(Looper looper, Handler.Callback callback) {
273 return Handler.createAsync(looper, callback);
274 }
275
276 public static boolean postDelayed(Handler handler, Runnable r, Object token,
277 long delayMillis) {
278 return handler.postDelayed(r, token, delayMillis);
279 }
Jake Wharton2d521652018-03-21 11:53:41 -0400280 }
281}