blob: 5ce1f8b38c8cafd4069d420fd379ca1a28e633b1 [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.support.v7.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
import android.content.Context;
import android.support.annotation.RestrictTo;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityManager;
/**
* Event handler used used to emulate the behavior of {@link View#setTooltipText(CharSequence)}
* prior to API level 26.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverListener,
View.OnAttachStateChangeListener {
private static final String TAG = "TooltipCompatHandler";
private static final long LONG_CLICK_HIDE_TIMEOUT_MS = 2500;
private static final long HOVER_HIDE_TIMEOUT_MS = 15000;
private static final long HOVER_HIDE_TIMEOUT_SHORT_MS = 3000;
private final View mAnchor;
private final CharSequence mTooltipText;
private final Runnable mShowRunnable = new Runnable() {
@Override
public void run() {
show(false /* not from touch*/);
}
};
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
hide();
}
};
private int mAnchorX;
private int mAnchorY;
private TooltipPopup mPopup;
private boolean mFromTouch;
// The handler currently showing a tooltip (there can be only one).
private static TooltipCompatHandler sActiveHandler;
/**
* Set the tooltip text for the view.
*
* @param view view to set the tooltip on
* @param tooltipText the tooltip text
*/
public static void setTooltipText(View view, CharSequence tooltipText) {
if (TextUtils.isEmpty(tooltipText)) {
if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
sActiveHandler.hide();
}
view.setOnLongClickListener(null);
view.setLongClickable(false);
view.setOnHoverListener(null);
} else {
new TooltipCompatHandler(view, tooltipText);
}
}
private TooltipCompatHandler(View anchor, CharSequence tooltipText) {
mAnchor = anchor;
mTooltipText = tooltipText;
mAnchor.setOnLongClickListener(this);
mAnchor.setOnHoverListener(this);
}
@Override
public boolean onLongClick(View v) {
mAnchorX = v.getWidth() / 2;
mAnchorY = v.getHeight() / 2;
show(true /* from touch */);
return true;
}
@Override
public boolean onHover(View v, MotionEvent event) {
if (mPopup != null && mFromTouch) {
return false;
}
AccessibilityManager manager = (AccessibilityManager)
mAnchor.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_MOVE:
if (mAnchor.isEnabled() && mPopup == null) {
mAnchorX = (int) event.getX();
mAnchorY = (int) event.getY();
mAnchor.removeCallbacks(mShowRunnable);
mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
}
break;
case MotionEvent.ACTION_HOVER_EXIT:
hide();
break;
}
return false;
}
@Override
public void onViewAttachedToWindow(View v) {
// no-op.
}
@Override
public void onViewDetachedFromWindow(View v) {
hide();
}
private void show(boolean fromTouch) {
if (!ViewCompat.isAttachedToWindow(mAnchor)) {
return;
}
if (sActiveHandler != null) {
sActiveHandler.hide();
}
sActiveHandler = this;
mFromTouch = fromTouch;
mPopup = new TooltipPopup(mAnchor.getContext());
mPopup.show(mAnchor, mAnchorX, mAnchorY, mFromTouch, mTooltipText);
// Only listen for attach state change while the popup is being shown.
mAnchor.addOnAttachStateChangeListener(this);
final long timeout;
if (mFromTouch) {
timeout = LONG_CLICK_HIDE_TIMEOUT_MS;
} else if ((ViewCompat.getWindowSystemUiVisibility(mAnchor)
& SYSTEM_UI_FLAG_LOW_PROFILE) == SYSTEM_UI_FLAG_LOW_PROFILE) {
timeout = HOVER_HIDE_TIMEOUT_SHORT_MS - ViewConfiguration.getLongPressTimeout();
} else {
timeout = HOVER_HIDE_TIMEOUT_MS - ViewConfiguration.getLongPressTimeout();
}
mAnchor.removeCallbacks(mHideRunnable);
mAnchor.postDelayed(mHideRunnable, timeout);
}
private void hide() {
if (sActiveHandler == this) {
sActiveHandler = null;
if (mPopup != null) {
mPopup.hide();
mPopup = null;
mAnchor.removeOnAttachStateChangeListener(this);
} else {
Log.e(TAG, "sActiveHandler.mPopup == null");
}
}
mAnchor.removeCallbacks(mShowRunnable);
mAnchor.removeCallbacks(mHideRunnable);
}
}