Home | History | Annotate | Download | only in display
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.display;
     18 
     19 import com.android.internal.util.DumpUtils;
     20 
     21 import android.content.Context;
     22 import android.graphics.SurfaceTexture;
     23 import android.hardware.display.DisplayManager;
     24 import android.util.Slog;
     25 import android.view.Display;
     26 import android.view.DisplayInfo;
     27 import android.view.GestureDetector;
     28 import android.view.Gravity;
     29 import android.view.LayoutInflater;
     30 import android.view.MotionEvent;
     31 import android.view.ScaleGestureDetector;
     32 import android.view.TextureView;
     33 import android.view.View;
     34 import android.view.WindowManager;
     35 import android.view.TextureView.SurfaceTextureListener;
     36 import android.widget.TextView;
     37 
     38 import java.io.PrintWriter;
     39 
     40 /**
     41  * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}.
     42  * <p>
     43  * This object must only be accessed on the UI thread.
     44  * No locks are held by this object and locks must not be held while making called into it.
     45  * </p>
     46  */
     47 final class OverlayDisplayWindow implements DumpUtils.Dump {
     48     private static final String TAG = "OverlayDisplayWindow";
     49     private static final boolean DEBUG = false;
     50 
     51     private final float INITIAL_SCALE = 0.5f;
     52     private final float MIN_SCALE = 0.3f;
     53     private final float MAX_SCALE = 1.0f;
     54     private final float WINDOW_ALPHA = 0.8f;
     55 
     56     // When true, disables support for moving and resizing the overlay.
     57     // The window is made non-touchable, which makes it possible to
     58     // directly interact with the content underneath.
     59     private final boolean DISABLE_MOVE_AND_RESIZE = false;
     60 
     61     private final Context mContext;
     62     private final String mName;
     63     private final int mWidth;
     64     private final int mHeight;
     65     private final int mDensityDpi;
     66     private final int mGravity;
     67     private final Listener mListener;
     68     private final String mTitle;
     69 
     70     private final DisplayManager mDisplayManager;
     71     private final WindowManager mWindowManager;
     72 
     73 
     74     private final Display mDefaultDisplay;
     75     private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
     76 
     77     private View mWindowContent;
     78     private WindowManager.LayoutParams mWindowParams;
     79     private TextureView mTextureView;
     80     private TextView mTitleTextView;
     81 
     82     private GestureDetector mGestureDetector;
     83     private ScaleGestureDetector mScaleGestureDetector;
     84 
     85     private boolean mWindowVisible;
     86     private int mWindowX;
     87     private int mWindowY;
     88     private float mWindowScale;
     89 
     90     private float mLiveTranslationX;
     91     private float mLiveTranslationY;
     92     private float mLiveScale = 1.0f;
     93 
     94     public OverlayDisplayWindow(Context context, String name,
     95             int width, int height, int densityDpi, int gravity, Listener listener) {
     96         mContext = context;
     97         mName = name;
     98         mWidth = width;
     99         mHeight = height;
    100         mDensityDpi = densityDpi;
    101         mGravity = gravity;
    102         mListener = listener;
    103         mTitle = context.getResources().getString(
    104                 com.android.internal.R.string.display_manager_overlay_display_title,
    105                 mName, mWidth, mHeight, mDensityDpi);
    106 
    107         mDisplayManager = (DisplayManager)context.getSystemService(
    108                 Context.DISPLAY_SERVICE);
    109         mWindowManager = (WindowManager)context.getSystemService(
    110                 Context.WINDOW_SERVICE);
    111 
    112         mDefaultDisplay = mWindowManager.getDefaultDisplay();
    113         updateDefaultDisplayInfo();
    114 
    115         createWindow();
    116     }
    117 
    118     public void show() {
    119         if (!mWindowVisible) {
    120             mDisplayManager.registerDisplayListener(mDisplayListener, null);
    121             if (!updateDefaultDisplayInfo()) {
    122                 mDisplayManager.unregisterDisplayListener(mDisplayListener);
    123                 return;
    124             }
    125 
    126             clearLiveState();
    127             updateWindowParams();
    128             mWindowManager.addView(mWindowContent, mWindowParams);
    129             mWindowVisible = true;
    130         }
    131     }
    132 
    133     public void dismiss() {
    134         if (mWindowVisible) {
    135             mDisplayManager.unregisterDisplayListener(mDisplayListener);
    136             mWindowManager.removeView(mWindowContent);
    137             mWindowVisible = false;
    138         }
    139     }
    140 
    141     public void relayout() {
    142         if (mWindowVisible) {
    143             updateWindowParams();
    144             mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
    145         }
    146     }
    147 
    148     @Override
    149     public void dump(PrintWriter pw) {
    150         pw.println("mWindowVisible=" + mWindowVisible);
    151         pw.println("mWindowX=" + mWindowX);
    152         pw.println("mWindowY=" + mWindowY);
    153         pw.println("mWindowScale=" + mWindowScale);
    154         pw.println("mWindowParams=" + mWindowParams);
    155         if (mTextureView != null) {
    156             pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX());
    157             pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY());
    158         }
    159         pw.println("mLiveTranslationX=" + mLiveTranslationX);
    160         pw.println("mLiveTranslationY=" + mLiveTranslationY);
    161         pw.println("mLiveScale=" + mLiveScale);
    162     }
    163 
    164     private boolean updateDefaultDisplayInfo() {
    165         if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
    166             Slog.w(TAG, "Cannot show overlay display because there is no "
    167                     + "default display upon which to show it.");
    168             return false;
    169         }
    170         return true;
    171     }
    172 
    173     private void createWindow() {
    174         LayoutInflater inflater = LayoutInflater.from(mContext);
    175 
    176         mWindowContent = inflater.inflate(
    177                 com.android.internal.R.layout.overlay_display_window, null);
    178         mWindowContent.setOnTouchListener(mOnTouchListener);
    179 
    180         mTextureView = (TextureView)mWindowContent.findViewById(
    181                 com.android.internal.R.id.overlay_display_window_texture);
    182         mTextureView.setPivotX(0);
    183         mTextureView.setPivotY(0);
    184         mTextureView.getLayoutParams().width = mWidth;
    185         mTextureView.getLayoutParams().height = mHeight;
    186         mTextureView.setOpaque(false);
    187         mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    188 
    189         mTitleTextView = (TextView)mWindowContent.findViewById(
    190                 com.android.internal.R.id.overlay_display_window_title);
    191         mTitleTextView.setText(mTitle);
    192 
    193         mWindowParams = new WindowManager.LayoutParams(
    194                 WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
    195         mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    196                 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    197                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    198                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    199                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    200         if (DISABLE_MOVE_AND_RESIZE) {
    201             mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    202         }
    203         mWindowParams.privateFlags |=
    204                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
    205         mWindowParams.alpha = WINDOW_ALPHA;
    206         mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
    207         mWindowParams.setTitle(mTitle);
    208 
    209         mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
    210         mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
    211 
    212         // Set the initial position and scale.
    213         // The position and scale will be clamped when the display is first shown.
    214         mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
    215                 0 : mDefaultDisplayInfo.logicalWidth;
    216         mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
    217                 0 : mDefaultDisplayInfo.logicalHeight;
    218         mWindowScale = INITIAL_SCALE;
    219     }
    220 
    221     private void updateWindowParams() {
    222         float scale = mWindowScale * mLiveScale;
    223         scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth);
    224         scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalHeight / mHeight);
    225         scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
    226 
    227         float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
    228         int width = (int)(mWidth * scale);
    229         int height = (int)(mHeight * scale);
    230         int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
    231         int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
    232         x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
    233         y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
    234 
    235         if (DEBUG) {
    236             Slog.d(TAG, "updateWindowParams: scale=" + scale
    237                     + ", offsetScale=" + offsetScale
    238                     + ", x=" + x + ", y=" + y
    239                     + ", width=" + width + ", height=" + height);
    240         }
    241 
    242         mTextureView.setScaleX(scale);
    243         mTextureView.setScaleY(scale);
    244 
    245         mWindowParams.x = x;
    246         mWindowParams.y = y;
    247         mWindowParams.width = width;
    248         mWindowParams.height = height;
    249     }
    250 
    251     private void saveWindowParams() {
    252         mWindowX = mWindowParams.x;
    253         mWindowY = mWindowParams.y;
    254         mWindowScale = mTextureView.getScaleX();
    255         clearLiveState();
    256     }
    257 
    258     private void clearLiveState() {
    259         mLiveTranslationX = 0f;
    260         mLiveTranslationY = 0f;
    261         mLiveScale = 1.0f;
    262     }
    263 
    264     private final DisplayManager.DisplayListener mDisplayListener =
    265             new DisplayManager.DisplayListener() {
    266         @Override
    267         public void onDisplayAdded(int displayId) {
    268         }
    269 
    270         @Override
    271         public void onDisplayChanged(int displayId) {
    272             if (displayId == mDefaultDisplay.getDisplayId()) {
    273                 if (updateDefaultDisplayInfo()) {
    274                     relayout();
    275                 } else {
    276                     dismiss();
    277                 }
    278             }
    279         }
    280 
    281         @Override
    282         public void onDisplayRemoved(int displayId) {
    283             if (displayId == mDefaultDisplay.getDisplayId()) {
    284                 dismiss();
    285             }
    286         }
    287     };
    288 
    289     private final SurfaceTextureListener mSurfaceTextureListener =
    290             new SurfaceTextureListener() {
    291         @Override
    292         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
    293                 int width, int height) {
    294             mListener.onWindowCreated(surfaceTexture, mDefaultDisplayInfo.refreshRate);
    295         }
    296 
    297         @Override
    298         public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    299             mListener.onWindowDestroyed();
    300             return true;
    301         }
    302 
    303         @Override
    304         public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
    305                 int width, int height) {
    306         }
    307 
    308         @Override
    309         public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    310         }
    311     };
    312 
    313     private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
    314         @Override
    315         public boolean onTouch(View view, MotionEvent event) {
    316             // Work in screen coordinates.
    317             final float oldX = event.getX();
    318             final float oldY = event.getY();
    319             event.setLocation(event.getRawX(), event.getRawY());
    320 
    321             mGestureDetector.onTouchEvent(event);
    322             mScaleGestureDetector.onTouchEvent(event);
    323 
    324             switch (event.getActionMasked()) {
    325                 case MotionEvent.ACTION_UP:
    326                 case MotionEvent.ACTION_CANCEL:
    327                     saveWindowParams();
    328                     break;
    329             }
    330 
    331             // Revert to window coordinates.
    332             event.setLocation(oldX, oldY);
    333             return true;
    334         }
    335     };
    336 
    337     private final GestureDetector.OnGestureListener mOnGestureListener =
    338             new GestureDetector.SimpleOnGestureListener() {
    339         @Override
    340         public boolean onScroll(MotionEvent e1, MotionEvent e2,
    341                 float distanceX, float distanceY) {
    342             mLiveTranslationX -= distanceX;
    343             mLiveTranslationY -= distanceY;
    344             relayout();
    345             return true;
    346         }
    347     };
    348 
    349     private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
    350             new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    351         @Override
    352         public boolean onScale(ScaleGestureDetector detector) {
    353             mLiveScale *= detector.getScaleFactor();
    354             relayout();
    355             return true;
    356         }
    357     };
    358 
    359     /**
    360      * Watches for significant changes in the overlay display window lifecycle.
    361      */
    362     public interface Listener {
    363         public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate);
    364         public void onWindowDestroyed();
    365     }
    366 }