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