1 /*
2  * Copyright (C) 2012 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 package android.view;
17 
18 import com.android.ide.common.rendering.api.ILayoutLog;
19 import com.android.internal.lang.System_Delegate;
20 import com.android.layoutlib.bridge.Bridge;
21 import com.android.layoutlib.bridge.android.BridgeContext;
22 import com.android.layoutlib.bridge.impl.RenderAction;
23 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
24 import com.android.tools.layoutlib.annotations.Nullable;
25 
26 import java.lang.StackWalker.StackFrame;
27 import java.util.Optional;
28 
29 import static com.android.layoutlib.bridge.impl.RenderAction.getCurrentContext;
30 
31 /**
32  * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
33  *
34  * Through the layoutlib_create tool, the original  methods of Choreographer have been
35  * replaced by calls to methods of the same name in this delegate class.
36  *
37  */
38 public class Choreographer_Delegate {
39     @LayoutlibDelegate
getRefreshRate()40     public static float getRefreshRate() {
41         return 60.f;
42     }
43 
44     @LayoutlibDelegate
postCallbackDelayedInternal( Choreographer thiz, int callbackType, Object action, Object token, long delayMillis)45     public static void postCallbackDelayedInternal(
46             Choreographer thiz, int callbackType, Object action, Object token, long delayMillis) {
47         BridgeContext context = getCurrentContext();
48         if (context == null) {
49             if (!Thread.currentThread().getName().equals("kotlinx.coroutines.DefaultExecutor")) {
50                 return;
51             }
52             ClassLoader moduleClassLoader = findCallingClassLoader();
53             if (moduleClassLoader == null) {
54                 return;
55             }
56             context = RenderAction.findContextFor(moduleClassLoader);
57             if (context == null) {
58                 return;
59             }
60         }
61         if (callbackType != Choreographer.CALLBACK_ANIMATION) {
62             // Ignore non-animation callbacks
63             return;
64         }
65         if (action == null) {
66             Bridge.getLog().error(ILayoutLog.TAG_BROKEN,
67                     "Callback with null action", (Object) null, null);
68         }
69         context.getSessionInteractiveData().getChoreographerCallbacks().add(action,
70                 token, delayMillis);
71     }
72 
73     @LayoutlibDelegate
removeCallbacksInternal( Choreographer thiz, int callbackType, Object action, Object token)74     public static void removeCallbacksInternal(
75             Choreographer thiz, int callbackType, Object action, Object token) {
76         BridgeContext context = getCurrentContext();
77         if (context == null) {
78             return;
79         }
80         if (callbackType != Choreographer.CALLBACK_ANIMATION) {
81             // Ignore non-animation callbacks
82             return;
83         }
84         context.getSessionInteractiveData().getChoreographerCallbacks().remove(action, token);
85     }
86 
87     @LayoutlibDelegate
getFrameTimeNanos(Choreographer thiz)88     public static long getFrameTimeNanos(Choreographer thiz) {
89         return System.nanoTime();
90     }
91 
92     /**
93      * With this method we are trying to find a child ClassLoader that calls this method. We assume
94      * that the child ClassLoader is the first ClassLoader in the callstack that is different from
95      * the current one.
96      */
97     @Nullable
findCallingClassLoader()98     private static ClassLoader findCallingClassLoader() {
99         final ClassLoader current = Choreographer_Delegate.class.getClassLoader();
100         StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
101         try {
102             return walker.walk(stackFrameStream -> {
103                 Optional<StackFrame> stackFrame = stackFrameStream
104                         .filter(sf -> sf.getDeclaringClass().getClassLoader() != current)
105                         .findFirst();
106                 return stackFrame.map(f -> f.getDeclaringClass().getClassLoader()).orElse(null);
107             });
108         } catch (Throwable ex) {
109             return null;
110         }
111     }
112 }
113