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.ThreadedRenderer;
     34 import android.view.View;
     35 import android.view.WindowManager;
     36 import android.view.TextureView.SurfaceTextureListener;
     37 import android.widget.TextView;
     38 
     39 import java.io.PrintWriter;
     40 
     41 /**
     42  * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}.
     43  * <p>
     44  * This object must only be accessed on the UI thread.
     45  * No locks are held by this object and locks must not be held while making called into it.
     46  * </p>
     47  */
     48 final class OverlayDisplayWindow implements DumpUtils.Dump {
     49     private static final String TAG = "OverlayDisplayWindow";
     50     private static final boolean DEBUG = false;
     51 
     52     private final float INITIAL_SCALE = 0.5f;
     53     private final float MIN_SCALE = 0.3f;
     54     private final float MAX_SCALE = 1.0f;
     55     private final float WINDOW_ALPHA = 0.8f;
     56 
     57     // When true, disables support for moving and resizing the overlay.
     58     // The window is made non-touchable, which makes it possible to
     59     // directly interact with the content underneath.
     60     private final boolean DISABLE_MOVE_AND_RESIZE = false;
     61 
     62     private final Context mContext;
     63     private final String mName;
     64     private int mWidth;
     65     private int mHeight;
     66     private int mDensityDpi;
     67     private final int mGravity;
     68     private final boolean mSecure;
     69     private final Listener mListener;
     70     private String mTitle;
     71 
     72     private final DisplayManager mDisplayManager;
     73     private final WindowManager mWindowManager;
     74 
     75 
     76     private final Display mDefaultDisplay;
     77     private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
     78 
     79     private View mWindowContent;
     80     private WindowManager.LayoutParams mWindowParams;
     81     private TextureView mTextureView;
     82     private TextView mTitleTextView;
     83 
     84     private GestureDetector mGestureDetector;
     85     private ScaleGestureDetector mScaleGestureDetector;
     86 
     87     private boolean mWindowVisible;
     88     private int mWindowX;
     89     private int mWindowY;
     90     private float mWindowScale;
     91 
     92     private float mLiveTranslationX;
     93     private float mLiveTranslationY;
     94     private float mLiveScale = 1.0f;
     95 
     96     public OverlayDisplayWindow(Context context, String name,
     97             int width, int height, int densityDpi, int gravity, boolean secure,
     98             Listener listener) {
     99         // Workaround device freeze (b/38372997)
    100         ThreadedRenderer.disableVsync();
    101         mContext = context;
    102         mName = name;
    103         mGravity = gravity;
    104         mSecure = secure;
    105         mListener = listener;
    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         resize(width, height, densityDpi, false /* doLayout */);
    116 
    117         createWindow();
    118     }
    119 
    120     public void show() {
    121         if (!mWindowVisible) {
    122             mDisplayManager.registerDisplayListener(mDisplayListener, null);
    123             if (!updateDefaultDisplayInfo()) {
    124                 mDisplayManager.unregisterDisplayListener(mDisplayListener);
    125                 return;
    126             }
    127 
    128             clearLiveState();
    129             updateWindowParams();
    130             mWindowManager.addView(mWindowContent, mWindowParams);
    131             mWindowVisible = true;
    132         }
    133     }
    134 
    135     public void dismiss() {
    136         if (mWindowVisible) {
    137             mDisplayManager.unregisterDisplayListener(mDisplayListener);
    138             mWindowManager.removeView(mWindowContent);
    139             mWindowVisible = false;
    140         }
    141     }
    142 
    143     public void resize(int width, int height, int densityDpi) {
    144         resize(width, height, densityDpi, true /* doLayout */);
    145     }
    146 
    147     private void resize(int width, int height, int densityDpi, boolean doLayout) {
    148         mWidth = width;
    149         mHeight = height;
    150         mDensityDpi = densityDpi;
    151         mTitle = mContext.getResources().getString(
    152                 com.android.internal.R.string.display_manager_overlay_display_title,
    153                 mName, mWidth, mHeight, mDensityDpi);
    154         if (mSecure) {
    155             mTitle += mContext.getResources().getString(
    156                     com.android.internal.R.string.display_manager_overlay_display_secure_suffix);
    157         }
    158         if (doLayout) {
    159             relayout();
    160         }
    161     }
    162 
    163     public void relayout() {
    164         if (mWindowVisible) {
    165             updateWindowParams();
    166             mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
    167         }
    168     }
    169 
    170     @Override
    171     public void dump(PrintWriter pw, String prefix) {
    172         pw.println("mWindowVisible=" + mWindowVisible);
    173         pw.println("mWindowX=" + mWindowX);
    174         pw.println("mWindowY=" + mWindowY);
    175         pw.println("mWindowScale=" + mWindowScale);
    176         pw.println("mWindowParams=" + mWindowParams);
    177         if (mTextureView != null) {
    178             pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX());
    179             pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY());
    180         }
    181         pw.println("mLiveTranslationX=" + mLiveTranslationX);
    182         pw.println("mLiveTranslationY=" + mLiveTranslationY);
    183         pw.println("mLiveScale=" + mLiveScale);
    184     }
    185 
    186     private boolean updateDefaultDisplayInfo() {
    187         if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
    188             Slog.w(TAG, "Cannot show overlay display because there is no "
    189                     + "default display upon which to show it.");
    190             return false;
    191         }
    192         return true;
    193     }
    194 
    195     private void createWindow() {
    196         LayoutInflater inflater = LayoutInflater.from(mContext);
    197 
    198         mWindowContent = inflater.inflate(
    199                 com.android.internal.R.layout.overlay_display_window, null);
    200         mWindowContent.setOnTouchListener(mOnTouchListener);
    201 
    202         mTextureView = (TextureView)mWindowContent.findViewById(
    203                 com.android.internal.R.id.overlay_display_window_texture);
    204         mTextureView.setPivotX(0);
    205         mTextureView.setPivotY(0);
    206         mTextureView.getLayoutParams().width = mWidth;
    207         mTextureView.getLayoutParams().height = mHeight;
    208         mTextureView.setOpaque(false);
    209         mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    210 
    211         mTitleTextView = (TextView)mWindowContent.findViewById(
    212                 com.android.internal.R.id.overlay_display_window_title);
    213         mTitleTextView.setText(mTitle);
    214 
    215         mWindowParams = new WindowManager.LayoutParams(
    216                 WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
    217         mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    218                 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    219                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    220                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    221                 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    222         if (mSecure) {
    223             mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE;
    224         }
    225         if (DISABLE_MOVE_AND_RESIZE) {
    226             mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    227         }
    228         mWindowParams.privateFlags |=
    229                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
    230         mWindowParams.alpha = WINDOW_ALPHA;
    231         mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
    232         mWindowParams.setTitle(mTitle);
    233 
    234         mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
    235         mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
    236 
    237         // Set the initial position and scale.
    238         // The position and scale will be clamped when the display is first shown.
    239         mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
    240                 0 : mDefaultDisplayInfo.logicalWidth;
    241         mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
    242                 0 : mDefaultDisplayInfo.logicalHeight;
    243         mWindowScale = INITIAL_SCALE;
    244     }
    245 
    246     private void updateWindowParams() {
    247         float scale = mWindowScale * mLiveScale;
    248         scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth);
    249         scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalHeight / mHeight);
    250         scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
    251 
    252         float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
    253         int width = (int)(mWidth * scale);
    254         int height = (int)(mHeight * scale);
    255         int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
    256         int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
    257         x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
    258         y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
    259 
    260         if (DEBUG) {
    261             Slog.d(TAG, "updateWindowParams: scale=" + scale
    262                     + ", offsetScale=" + offsetScale
    263                     + ", x=" + x + ", y=" + y
    264                     + ", width=" + width + ", height=" + height);
    265         }
    266 
    267         mTextureView.setScaleX(scale);
    268         mTextureView.setScaleY(scale);
    269 
    270         mWindowParams.x = x;
    271         mWindowParams.y = y;
    272         mWindowParams.width = width;
    273         mWindowParams.height = height;
    274     }
    275 
    276     private void saveWindowParams() {
    277         mWindowX = mWindowParams.x;
    278         mWindowY = mWindowParams.y;
    279         mWindowScale = mTextureView.getScaleX();
    280         clearLiveState();
    281     }
    282 
    283     private void clearLiveState() {
    284         mLiveTranslationX = 0f;
    285         mLiveTranslationY = 0f;
    286         mLiveScale = 1.0f;
    287     }
    288 
    289     private final DisplayManager.DisplayListener mDisplayListener =
    290             new DisplayManager.DisplayListener() {
    291         @Override
    292         public void onDisplayAdded(int displayId) {
    293         }
    294 
    295         @Override
    296         public void onDisplayChanged(int displayId) {
    297             if (displayId == mDefaultDisplay.getDisplayId()) {
    298                 if (updateDefaultDisplayInfo()) {
    299                     relayout();
    300                     mListener.onStateChanged(mDefaultDisplayInfo.state);
    301                 } else {
    302                     dismiss();
    303                 }
    304             }
    305         }
    306 
    307         @Override
    308         public void onDisplayRemoved(int displayId) {
    309             if (displayId == mDefaultDisplay.getDisplayId()) {
    310                 dismiss();
    311             }
    312         }
    313     };
    314 
    315     private final SurfaceTextureListener mSurfaceTextureListener =
    316             new SurfaceTextureListener() {
    317         @Override
    318         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
    319                 int width, int height) {
    320             mListener.onWindowCreated(surfaceTexture,
    321                     mDefaultDisplayInfo.getMode().getRefreshRate(),
    322                     mDefaultDisplayInfo.presentationDeadlineNanos, mDefaultDisplayInfo.state);
    323         }
    324 
    325         @Override
    326         public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    327             mListener.onWindowDestroyed();
    328             return true;
    329         }
    330 
    331         @Override
    332         public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
    333                 int width, int height) {
    334         }
    335 
    336         @Override
    337         public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    338         }
    339     };
    340 
    341     private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
    342         @Override
    343         public boolean onTouch(View view, MotionEvent event) {
    344             // Work in screen coordinates.
    345             final float oldX = event.getX();
    346             final float oldY = event.getY();
    347             event.setLocation(event.getRawX(), event.getRawY());
    348 
    349             mGestureDetector.onTouchEvent(event);
    350             mScaleGestureDetector.onTouchEvent(event);
    351 
    352             switch (event.getActionMasked()) {
    353                 case MotionEvent.ACTION_UP:
    354                 case MotionEvent.ACTION_CANCEL:
    355                     saveWindowParams();
    356                     break;
    357             }
    358 
    359             // Revert to window coordinates.
    360             event.setLocation(oldX, oldY);
    361             return true;
    362         }
    363     };
    364 
    365     private final GestureDetector.OnGestureListener mOnGestureListener =
    366             new GestureDetector.SimpleOnGestureListener() {
    367         @Override
    368         public boolean onScroll(MotionEvent e1, MotionEvent e2,
    369                 float distanceX, float distanceY) {
    370             mLiveTranslationX -= distanceX;
    371             mLiveTranslationY -= distanceY;
    372             relayout();
    373             return true;
    374         }
    375     };
    376 
    377     private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
    378             new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    379         @Override
    380         public boolean onScale(ScaleGestureDetector detector) {
    381             mLiveScale *= detector.getScaleFactor();
    382             relayout();
    383             return true;
    384         }
    385     };
    386 
    387     /**
    388      * Watches for significant changes in the overlay display window lifecycle.
    389      */
    390     public interface Listener {
    391         public void onWindowCreated(SurfaceTexture surfaceTexture,
    392                 float refreshRate, long presentationDeadlineNanos, int state);
    393         public void onWindowDestroyed();
    394         public void onStateChanged(int state);
    395     }
    396 }
    397