• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
4 import static android.os.Build.VERSION_CODES.LOLLIPOP;
5 import static org.robolectric.RuntimeEnvironment.getApiLevel;
6 import static org.robolectric.shadow.api.Shadow.directlyOn;
7 import static org.robolectric.util.ReflectionHelpers.getField;
8 import static org.robolectric.util.ReflectionHelpers.getStaticField;
9 import static org.robolectric.util.ReflectionHelpers.setField;
10 import static org.robolectric.util.ReflectionHelpers.setStaticField;
11 
12 import android.os.Handler;
13 import android.os.Looper;
14 import android.os.Message;
15 import android.os.MessageQueue;
16 import org.robolectric.annotation.HiddenApi;
17 import org.robolectric.annotation.Implementation;
18 import org.robolectric.annotation.Implements;
19 import org.robolectric.annotation.RealObject;
20 import org.robolectric.annotation.Resetter;
21 import org.robolectric.shadow.api.Shadow;
22 
23 @Implements(Message.class)
24 public class ShadowMessage {
25   @RealObject
26   private Message realMessage;
27   private Runnable scheduledRunnable;
28 
29   private static final Object lock = getStaticField(Message.class, "sPoolSync");
30 
unschedule()31   private void unschedule() {
32     Handler target = realMessage.getTarget();
33 
34 	if (target != null && scheduledRunnable != null) {
35 	  shadowOf(target.getLooper()).getScheduler().remove(scheduledRunnable);
36 	  scheduledRunnable = null;
37 	}
38   }
39 
40   /**
41    * Hook to unscheduled the callback when the message is recycled.
42    * Invokes {@link #unschedule()} and then calls through to the
43    * package private method {@link Message#recycleUnchecked()}
44    * on the real object.
45    */
46   @HiddenApi
47   @Implementation(minSdk = LOLLIPOP)
recycleUnchecked()48   public void recycleUnchecked() {
49     if (getApiLevel() >= LOLLIPOP) {
50       unschedule();
51       directlyOn(realMessage, Message.class, "recycleUnchecked");
52     } else {
53       // provide forward compatibility with SDK 21.
54       recycle();
55     }
56   }
57 
58   /**
59    * Hook to unscheduled the callback when the message is recycled. Invokes {@link #unschedule()}
60    * and then calls through to {@link Message#recycle()} on the real object.
61    */
62   @Implementation(maxSdk = KITKAT_WATCH)
recycle()63   protected void recycle() {
64     unschedule();
65     directlyOn(realMessage, Message.class, "recycle");
66   }
67 
68   /**
69    * Stores the {@link Runnable} instance that has been scheduled
70    * to invoke this message. This is called when the message is
71    * enqueued by {@link ShadowMessageQueue#enqueueMessage} and is used when
72    * the message is recycled to ensure that the correct
73    * {@link Runnable} instance is removed from the associated scheduler.
74    *
75    * @param r the {@link Runnable} instance that is scheduled to
76    * trigger this message.
77    *
78 #if ($api >= 21)   * @see #recycleUnchecked()
79 #else   * @see #recycle()
80 #end
81    */
setScheduledRunnable(Runnable r)82   public void setScheduledRunnable(Runnable r) {
83     scheduledRunnable = r;
84   }
85 
86   /**
87    * Convenience method to provide access to the private {@code Message.isInUse()} method. Note that
88    * the definition of "in use" changed with API 21:
89    *
90    * <p>In API 19, a message was only considered "in use" during its dispatch. In API 21, the
91    * message is considered "in use" from the time it is enqueued until the time that it is freshly
92    * obtained via a call to {@link Message#obtain()}. This means that in API 21 messages that are in
93    * the recycled pool will still be marked as "in use".
94    *
95    * @return {@code true} if the message is currently "in use", {@code false} otherwise.
96    */
97   @Implementation
isInUse()98   protected boolean isInUse() {
99   	return directlyOn(realMessage, Message.class, "isInUse");
100   }
101 
102   /**
103    * Convenience method to provide getter access to the private field
104    * {@code Message.next}.
105    *
106    * @return The next message in the current message chain.
107    * @see #setNext(Message)
108    */
getNext()109   public Message getNext() {
110     return getField(realMessage, "next");
111   }
112 
113   /**
114    * Convenience method to provide setter access to the private field
115    * {@code Message.next}.
116    *
117    * @param next the new next message for the current message.
118    * @see #getNext()
119    */
setNext(Message next)120   public void setNext(Message next) {
121     setField(realMessage, "next", next);
122   }
123 
124   /**
125    * Resets the static state of the {@link Message} class by
126    * emptying the message pool.
127    */
128   @Resetter
reset()129   public static void reset() {
130     synchronized (lock) {
131       setStaticField(Message.class, "sPoolSize", 0);
132       setStaticField(Message.class, "sPool", null);
133     }
134   }
135 
shadowOf(Looper looper)136   private static ShadowLooper shadowOf(Looper looper) {
137     return Shadow.extract(looper);
138   }
139 
shadowOf(MessageQueue mq)140   private static ShadowMessageQueue shadowOf(MessageQueue mq) {
141     return Shadow.extract(mq);
142   }
143 }
144