Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package androidx.core.os; |
| 18 | |
Aurimas Liutikas | db91699 | 2018-06-06 17:04:06 -0700 | [diff] [blame] | 19 | import android.os.Build; |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 20 | import android.os.Handler; |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 21 | import android.os.Looper; |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 22 | import android.os.Message; |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 23 | import android.util.Log; |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 24 | |
| 25 | import androidx.annotation.NonNull; |
| 26 | import androidx.annotation.Nullable; |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 27 | import androidx.annotation.RequiresApi; |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 28 | |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 29 | import java.lang.reflect.InvocationTargetException; |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 30 | import java.lang.reflect.Method; |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 31 | |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 32 | /** |
| 33 | * Helper for accessing features in {@link Handler}. |
| 34 | */ |
| 35 | public final class HandlerCompat { |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 36 | 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 Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 45 | * @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 Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 55 | * |
| 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 Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 60 | @SuppressWarnings("JavaReflectionMemberAccess") |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 61 | @NonNull |
| 62 | public static Handler createAsync(@NonNull Looper looper) { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 63 | Exception wrappedException; |
| 64 | |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 65 | if (Build.VERSION.SDK_INT >= 28) { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 66 | return Api28Impl.createAsync(looper); |
| 67 | } else if (Build.VERSION.SDK_INT >= 17) { |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 68 | try { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 69 | // 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 Lam | 47ce1ab | 2020-10-26 21:14:57 -0700 | [diff] [blame] | 71 | return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class, |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 72 | boolean.class) |
| 73 | .newInstance(looper, null, true); |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 74 | } catch (IllegalAccessException e) { |
| 75 | wrappedException = e; |
| 76 | } catch (InstantiationException e) { |
| 77 | wrappedException = e; |
| 78 | } catch (NoSuchMethodException e) { |
| 79 | wrappedException = e; |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 80 | } 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 Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 90 | // 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 Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 94 | } |
| 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 Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 107 | * 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 Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 115 | * @param looper the Looper that the new Handler should be bound to |
Aurimas Liutikas | c7098ea | 2023-08-18 15:45:46 -0700 | [diff] [blame^] | 116 | * @param callback callback to send events to |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 117 | * @return a new async Handler instance |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 118 | * @see Handler#createAsync(Looper, Handler.Callback) |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 119 | */ |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 120 | @SuppressWarnings("JavaReflectionMemberAccess") |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 121 | @NonNull |
Dustin Lam | 47ce1ab | 2020-10-26 21:14:57 -0700 | [diff] [blame] | 122 | public static Handler createAsync(@NonNull Looper looper, @NonNull Handler.Callback callback) { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 123 | Exception wrappedException; |
| 124 | |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 125 | if (Build.VERSION.SDK_INT >= 28) { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 126 | return Api28Impl.createAsync(looper, callback); |
| 127 | } else if (Build.VERSION.SDK_INT >= 17) { |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 128 | try { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 129 | // 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 Lam | 47ce1ab | 2020-10-26 21:14:57 -0700 | [diff] [blame] | 131 | return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class, |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 132 | boolean.class) |
| 133 | .newInstance(looper, callback, true); |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 134 | } catch (IllegalAccessException e) { |
| 135 | wrappedException = e; |
| 136 | } catch (InstantiationException e) { |
| 137 | wrappedException = e; |
| 138 | } catch (NoSuchMethodException e) { |
| 139 | wrappedException = e; |
Jake Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 140 | } 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 Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 150 | // 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 Wharton | 1af4b8d | 2018-07-18 15:31:39 -0400 | [diff] [blame] | 154 | } |
| 155 | return new Handler(looper, callback); |
| 156 | } |
| 157 | |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 158 | /** |
| 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 Liutikas | c7098ea | 2023-08-18 15:45:46 -0700 | [diff] [blame^] | 166 | * @param handler handler to use for posting the runnable. |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 167 | * @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 Liutikas | db91699 | 2018-06-06 17:04:06 -0700 | [diff] [blame] | 184 | if (Build.VERSION.SDK_INT >= 28) { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 185 | return Api28Impl.postDelayed(handler, r, token, delayMillis); |
Jake Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 186 | } |
| 187 | |
| 188 | Message message = Message.obtain(handler, r); |
| 189 | message.obj = token; |
| 190 | return handler.sendMessageDelayed(message, delayMillis); |
| 191 | } |
| 192 | |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 193 | /** |
| 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 Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 247 | private HandlerCompat() { |
Alan Viverette | 7e9c4bf | 2021-02-08 17:41:41 -0500 | [diff] [blame] | 248 | // 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 Wharton | 2d52165 | 2018-03-21 11:53:41 -0400 | [diff] [blame] | 280 | } |
| 281 | } |