| /* |
| * Copyright (C) 2016 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 com.android.internal.view; |
| |
| import android.content.Context; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.util.Slog; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.view.WindowManagerGlobal; |
| import android.widget.TextView; |
| |
| public class TooltipPopup { |
| private static final String TAG = "TooltipPopup"; |
| |
| private final Context mContext; |
| |
| private final View mContentView; |
| private final TextView mMessageView; |
| |
| private final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(); |
| private final Rect mTmpDisplayFrame = new Rect(); |
| private final int[] mTmpAnchorPos = new int[2]; |
| private final int[] mTmpAppPos = new int[2]; |
| |
| public TooltipPopup(Context context) { |
| mContext = context; |
| |
| mContentView = LayoutInflater.from(mContext).inflate( |
| com.android.internal.R.layout.tooltip, null); |
| mMessageView = (TextView) mContentView.findViewById( |
| com.android.internal.R.id.message); |
| |
| mLayoutParams.setTitle( |
| mContext.getString(com.android.internal.R.string.tooltip_popup_title)); |
| mLayoutParams.packageName = mContext.getOpPackageName(); |
| mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; |
| mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; |
| mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; |
| mLayoutParams.format = PixelFormat.TRANSLUCENT; |
| mLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Tooltip; |
| mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; |
| } |
| |
| public void show(View anchorView, int anchorX, int anchorY, boolean fromTouch, |
| CharSequence tooltipText) { |
| if (isShowing()) { |
| hide(); |
| } |
| |
| mMessageView.setText(tooltipText); |
| |
| computePosition(anchorView, anchorX, anchorY, fromTouch, mLayoutParams); |
| |
| WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); |
| wm.addView(mContentView, mLayoutParams); |
| } |
| |
| public void hide() { |
| if (!isShowing()) { |
| return; |
| } |
| |
| WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); |
| wm.removeView(mContentView); |
| } |
| |
| public View getContentView() { |
| return mContentView; |
| } |
| |
| public boolean isShowing() { |
| return mContentView.getParent() != null; |
| } |
| |
| private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch, |
| WindowManager.LayoutParams outParams) { |
| outParams.token = anchorView.getApplicationWindowToken(); |
| |
| final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset( |
| com.android.internal.R.dimen.tooltip_precise_anchor_threshold); |
| |
| final int offsetX; |
| if (anchorView.getWidth() >= tooltipPreciseAnchorThreshold) { |
| // Wide view. Align the tooltip horizontally to the precise X position. |
| offsetX = anchorX; |
| } else { |
| // Otherwise anchor the tooltip to the view center. |
| offsetX = anchorView.getWidth() / 2; // Center on the view horizontally. |
| } |
| |
| final int offsetBelow; |
| final int offsetAbove; |
| if (anchorView.getHeight() >= tooltipPreciseAnchorThreshold) { |
| // Tall view. Align the tooltip vertically to the precise Y position. |
| final int offsetExtra = mContext.getResources().getDimensionPixelOffset( |
| com.android.internal.R.dimen.tooltip_precise_anchor_extra_offset); |
| offsetBelow = anchorY + offsetExtra; |
| offsetAbove = anchorY - offsetExtra; |
| } else { |
| // Otherwise anchor the tooltip to the view center. |
| offsetBelow = anchorView.getHeight(); // Place below the view in most cases. |
| offsetAbove = 0; // Place above the view if the tooltip does not fit below. |
| } |
| |
| outParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; |
| |
| final int tooltipOffset = mContext.getResources().getDimensionPixelOffset( |
| fromTouch ? com.android.internal.R.dimen.tooltip_y_offset_touch |
| : com.android.internal.R.dimen.tooltip_y_offset_non_touch); |
| |
| // Find the main app window. The popup window will be positioned relative to it. |
| final View appView = WindowManagerGlobal.getInstance().getWindowView( |
| anchorView.getApplicationWindowToken()); |
| if (appView == null) { |
| Slog.e(TAG, "Cannot find app view"); |
| return; |
| } |
| appView.getWindowVisibleDisplayFrame(mTmpDisplayFrame); |
| appView.getLocationOnScreen(mTmpAppPos); |
| |
| anchorView.getLocationOnScreen(mTmpAnchorPos); |
| mTmpAnchorPos[0] -= mTmpAppPos[0]; |
| mTmpAnchorPos[1] -= mTmpAppPos[1]; |
| // mTmpAnchorPos is now relative to the main app window. |
| |
| outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2; |
| |
| final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); |
| mContentView.measure(spec, spec); |
| final int tooltipHeight = mContentView.getMeasuredHeight(); |
| |
| final int yAbove = mTmpAnchorPos[1] + offsetAbove - tooltipOffset - tooltipHeight; |
| final int yBelow = mTmpAnchorPos[1] + offsetBelow + tooltipOffset; |
| if (fromTouch) { |
| if (yAbove >= 0) { |
| outParams.y = yAbove; |
| } else { |
| outParams.y = yBelow; |
| } |
| } else { |
| // Use mTmpDisplayFrame.height() as the lower boundary instead of appView.getHeight(), |
| // as the latter includes the navigation bar, and tooltips do not look good over |
| // the navigation bar. |
| if (yBelow + tooltipHeight <= mTmpDisplayFrame.height()) { |
| outParams.y = yBelow; |
| } else { |
| outParams.y = yAbove; |
| } |
| } |
| } |
| } |