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