@@ -147,10 +147,72 @@ private AccessibilityWindowInfo getAccessibilityWindowInfo() {
147147 if (mCachedWindow == null ) {
148148 throw new IllegalStateException ("This window has already been recycled." );
149149 }
150- // TODO(b/405371739): Implement the window staleness check when refresh() is accessible.
150+
151+ getDevice ().waitForIdle ();
152+ if (!refresh ()) {
153+ getDevice ().runWatchers ();
154+ if (!refresh ()) {
155+ throw new StaleObjectException ();
156+ }
157+ }
151158 return mCachedWindow ;
152159 }
153160
161+ /**
162+ * Refreshes this window's state if it is stale.
163+ *
164+ * <p>Ideally, this would call the hidden {@code AccessibilityWindowInfo.refresh()} method
165+ * for an in-place update. Since it is unavailable, for older API levels, it uses a fallback
166+ * ({@link #refreshFromPool()}) that finds the window info from the refreshed root node.
167+ *
168+ * @return {@code true} if the refresh succeeded, {@code false} if this window is stale and
169+ * no longer exists.
170+ */
171+ private boolean refresh () {
172+ // TODO(b/405371739): Extend this to handle different API level behaviors when the
173+ // refresh API is available in the future SDK.
174+ return refreshFromPool ();
175+ }
176+
177+ /**
178+ * Refreshes this window by replacing it with an updated instance from the pool.
179+ *
180+ * <p>This is a fallback for SDKs where the refresh system API is unavailable. To refresh the
181+ * info, it returns the old cached window to the pool, then obtains a new one (which may or
182+ * may not be the one just recycled) from the pool based on the refreshed root node.
183+ * <p>Note: This will effectively update the window but not in-place, so the underlying cached
184+ * window may reference a new object each time this method is called.
185+ */
186+ private boolean refreshFromPool () {
187+ AccessibilityNodeInfo rootNode = mCachedWindow .getRoot ();
188+ if (rootNode == null ) {
189+ return false ;
190+ }
191+ AccessibilityWindowInfo refreshedWindow = null ;
192+ try {
193+ if (!rootNode .refresh ()) {
194+ return false ;
195+ }
196+ refreshedWindow = rootNode .getWindow ();
197+ if (refreshedWindow == null || refreshedWindow .getId () != mCachedWindow .getId ()) {
198+ return false ;
199+ }
200+ // This is a best effort to keep the cached window referencing the same object from
201+ // the pool by recycling the old info then immediately acquiring it back for the
202+ // refreshed info.
203+ mCachedWindow .recycle ();
204+ mCachedWindow = AccessibilityWindowInfo .obtain (refreshedWindow );
205+ return true ;
206+ } finally {
207+ if (rootNode != null ) {
208+ rootNode .recycle ();
209+ }
210+ if (refreshedWindow != null ) {
211+ refreshedWindow .recycle ();
212+ }
213+ }
214+ }
215+
154216 /** Recycles this window. */
155217 private void recycle () {
156218 mCachedWindow .recycle ();
0 commit comments