LocationBasedViewTracker.java
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.slice.widget;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.RestrictTo;
import java.util.ArrayList;
/**
* Utility class to track view based on relative location to the parent.
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class LocationBasedViewTracker implements Runnable, View.OnLayoutChangeListener {
private static final SelectionLogic INPUT_FOCUS = new SelectionLogic() {
@Override
public void selectView(View view) {
view.requestFocus();
}
};
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static final SelectionLogic A11Y_FOCUS = new SelectionLogic() {
@Override
public void selectView(View view) {
view.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
}
};
private final Rect mFocusRect = new Rect();
private final ViewGroup mParent;
private final SelectionLogic mSelectionLogic;
private LocationBasedViewTracker(ViewGroup parent, View selected,
SelectionLogic selectionLogic) {
mParent = parent;
mSelectionLogic = selectionLogic;
selected.getDrawingRect(mFocusRect);
parent.offsetDescendantRectToMyCoords(selected, mFocusRect);
// Request a layout pass immediately after storing the view position. This ensure that we
// get onLayoutChange before the selected view can change as a result of user interaction.
mParent.addOnLayoutChangeListener(this);
mParent.requestLayout();
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
mParent.removeOnLayoutChangeListener(this);
mParent.post(this);
}
@Override
public void run() {
ArrayList<View> views = new ArrayList<>();
mParent.addFocusables(views, View.FOCUS_FORWARD, View.FOCUSABLES_ALL);
Rect temp = new Rect();
int oldCloseness = Integer.MAX_VALUE;
View closestView = null;
for (View v : views) {
v.getDrawingRect(temp);
mParent.offsetDescendantRectToMyCoords(v, temp);
if (!mFocusRect.intersect(temp)) {
continue;
}
// Find closeness
int closeness = Math.abs(mFocusRect.left - temp.left)
+ Math.abs(mFocusRect.right - temp.right)
+ Math.abs(mFocusRect.top - temp.top)
+ Math.abs(mFocusRect.bottom - temp.bottom);
if (oldCloseness > closeness) {
oldCloseness = closeness;
closestView = v;
}
}
if (closestView != null) {
mSelectionLogic.selectView(closestView);
}
}
/**
* Tries to preserve the input focus after the next content change
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static void trackInputFocused(ViewGroup parent) {
View focused = parent.findFocus();
if (focused != null) {
new LocationBasedViewTracker(parent, focused, INPUT_FOCUS);
}
}
/**
* Tries to preserve the accessibility focus after the next content change
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static void trackA11yFocus(ViewGroup parent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
if (!((AccessibilityManager) parent.getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE)).isTouchExplorationEnabled()) {
return;
}
ArrayList<View> children = new ArrayList<>();
parent.addFocusables(children, View.FOCUS_FORWARD, View.FOCUSABLES_ALL);
View focused = null;
for (View child : children) {
if (child.isAccessibilityFocused()) {
focused = child;
break;
}
}
if (focused != null) {
new LocationBasedViewTracker(parent, focused, A11Y_FOCUS);
}
}
/**
* Interface to control how a view is selected
*/
private interface SelectionLogic {
void selectView(View view);
}
}