1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.S;
4 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
5 import static org.robolectric.util.reflector.Reflector.reflector;
6 
7 import android.os.Looper;
8 import android.view.Choreographer;
9 import android.view.ThreadedRenderer;
10 import com.google.common.util.concurrent.Uninterruptibles;
11 import java.util.List;
12 import java.util.concurrent.CopyOnWriteArrayList;
13 import org.robolectric.annotation.Implementation;
14 import org.robolectric.annotation.Implements;
15 import org.robolectric.annotation.LooperMode;
16 import org.robolectric.annotation.LooperMode.Mode;
17 import org.robolectric.annotation.RealObject;
18 import org.robolectric.config.ConfigurationRegistry;
19 import org.robolectric.shadow.api.Shadow;
20 import org.robolectric.util.reflector.Accessor;
21 import org.robolectric.util.reflector.ForType;
22 
23 /** Shadow for {@link BackdropFrameRenderer} */
24 @Implements(
25     className = "com.android.internal.policy.BackdropFrameRenderer",
26     minSdk = S,
27     maxSdk = UPSIDE_DOWN_CAKE,
28     isInAndroidSdk = false)
29 public class ShadowBackdropFrameRenderer {
30 
31   // Updated to the real value in the generated Shadow constructor
32   @RealObject private final Object realBackdropFrameRenderer = null;
33   private Looper looper;
34 
35   private static final List<Object> activeRenderers = new CopyOnWriteArrayList<>();
36 
37   @Implementation
run()38   protected void run() {
39     try {
40       Looper.prepare();
41       activeRenderers.add(realBackdropFrameRenderer);
42       looper = Looper.myLooper();
43       synchronized (realBackdropFrameRenderer) {
44         ThreadedRenderer renderer =
45             reflector(BackdropFrameRendererReflector.class, realBackdropFrameRenderer)
46                 .getRenderer();
47         if (renderer == null) {
48           // This can happen if 'releaseRenderer' is called immediately after 'start'.
49           return;
50         }
51         reflector(BackdropFrameRendererReflector.class, realBackdropFrameRenderer)
52             .setChoreographer(Choreographer.getInstance());
53       }
54       Looper.loop();
55     } finally {
56       reflector(BackdropFrameRendererReflector.class, realBackdropFrameRenderer).releaseRenderer();
57     }
58     synchronized (realBackdropFrameRenderer) {
59       reflector(BackdropFrameRendererReflector.class, realBackdropFrameRenderer)
60           .setChoreographer(null);
61       Choreographer.releaseInstance();
62     }
63   }
64 
65   // called from ShadowChoreographer to ensure choreographer is unpaused before this is executed
reset()66   static void reset() {
67     for (Object renderer : activeRenderers) {
68       reflector(BackdropFrameRendererReflector.class, renderer).releaseRenderer();
69       // Explicitly quit the looper if in legacy looper mode - otherwise it will hang forever
70       if (ConfigurationRegistry.get(LooperMode.Mode.class) == Mode.LEGACY) {
71         ShadowBackdropFrameRenderer shadowBackdropFrameRenderer = Shadow.extract(renderer);
72         shadowBackdropFrameRenderer.looper.quit();
73       }
74       Uninterruptibles.joinUninterruptibly((Thread) renderer);
75       activeRenderers.remove(renderer);
76     }
77   }
78 
79   @ForType(className = "com.android.internal.policy.BackdropFrameRenderer")
80   interface BackdropFrameRendererReflector {
releaseRenderer()81     void releaseRenderer();
82 
83     @Accessor("mRenderer")
getRenderer()84     ThreadedRenderer getRenderer();
85 
86     @Accessor("mChoreographer")
setChoreographer(Choreographer c)87     void setChoreographer(Choreographer c);
88 
89     @Accessor("mChoreographer")
getChoreographer()90     Choreographer getChoreographer();
91   }
92 }
93