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