Home | History | Annotate | Download | only in player
      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.example.android.mediarouter.player;
     18 
     19 import android.content.Context;
     20 import android.graphics.SurfaceTexture;
     21 import android.hardware.display.DisplayManager;
     22 import android.os.Build;
     23 import android.util.DisplayMetrics;
     24 import android.util.Log;
     25 import android.view.Display;
     26 import android.view.GestureDetector;
     27 import android.view.Gravity;
     28 import android.view.LayoutInflater;
     29 import android.view.MotionEvent;
     30 import android.view.ScaleGestureDetector;
     31 import android.view.Surface;
     32 import android.view.SurfaceHolder;
     33 import android.view.SurfaceView;
     34 import android.view.TextureView;
     35 import android.view.TextureView.SurfaceTextureListener;
     36 import android.view.View;
     37 import android.view.WindowManager;
     38 import android.widget.TextView;
     39 
     40 import com.example.android.mediarouter.R;
     41 
     42 /**
     43  * Manages an overlay display window, used for simulating remote playback.
     44  */
     45 public abstract class OverlayDisplayWindow {
     46     private static final String TAG = "OverlayDisplayWindow";
     47     private static final boolean DEBUG = false;
     48 
     49     private static final float WINDOW_ALPHA = 0.8f;
     50     private static final float INITIAL_SCALE = 0.5f;
     51     private static final float MIN_SCALE = 0.3f;
     52     private static final float MAX_SCALE = 1.0f;
     53 
     54     protected final Context mContext;
     55     protected final String mName;
     56     protected final int mWidth;
     57     protected final int mHeight;
     58     protected final int mGravity;
     59     protected OverlayWindowListener mListener;
     60 
     61     protected OverlayDisplayWindow(Context context, String name,
     62             int width, int height, int gravity) {
     63         mContext = context;
     64         mName = name;
     65         mWidth = width;
     66         mHeight = height;
     67         mGravity = gravity;
     68     }
     69 
     70     public static OverlayDisplayWindow create(Context context, String name,
     71             int width, int height, int gravity) {
     72         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
     73             return new JellybeanMr1Impl(context, name, width, height, gravity);
     74         } else {
     75             return new LegacyImpl(context, name, width, height, gravity);
     76         }
     77     }
     78 
     79     public void setOverlayWindowListener(OverlayWindowListener listener) {
     80         mListener = listener;
     81     }
     82 
     83     public Context getContext() {
     84         return mContext;
     85     }
     86 
     87     public abstract void show();
     88 
     89     public abstract void dismiss();
     90 
     91     public abstract void updateAspectRatio(int width, int height);
     92 
     93     // Watches for significant changes in the overlay display window lifecycle.
     94     public interface OverlayWindowListener {
     95         public void onWindowCreated(Surface surface);
     96         public void onWindowCreated(SurfaceHolder surfaceHolder);
     97         public void onWindowDestroyed();
     98     }
     99 
    100     /**
    101      * Implementation for older versions.
    102      */
    103     private static final class LegacyImpl extends OverlayDisplayWindow {
    104         private final WindowManager mWindowManager;
    105 
    106         private boolean mWindowVisible;
    107         private SurfaceView mSurfaceView;
    108 
    109         public LegacyImpl(Context context, String name,
    110                 int width, int height, int gravity) {
    111             super(context, name, width, height, gravity);
    112 
    113             mWindowManager = (WindowManager)context.getSystemService(
    114                     Context.WINDOW_SERVICE);
    115         }
    116 
    117         @Override
    118         public void show() {
    119             if (!mWindowVisible) {
    120                 mSurfaceView = new SurfaceView(mContext);
    121 
    122                 Display display = mWindowManager.getDefaultDisplay();
    123 
    124                 WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    125                         WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    126                 params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    127                         | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    128                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    129                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    130                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    131                 params.alpha = WINDOW_ALPHA;
    132                 params.gravity = Gravity.LEFT | Gravity.BOTTOM;
    133                 params.setTitle(mName);
    134 
    135                 int width = (int)(display.getWidth() * INITIAL_SCALE);
    136                 int height = (int)(display.getHeight() * INITIAL_SCALE);
    137                 if (mWidth > mHeight) {
    138                     height = mHeight * width / mWidth;
    139                 } else {
    140                     width = mWidth * height / mHeight;
    141                 }
    142                 params.width = width;
    143                 params.height = height;
    144 
    145                 mWindowManager.addView(mSurfaceView, params);
    146                 mWindowVisible = true;
    147 
    148                 SurfaceHolder holder = mSurfaceView.getHolder();
    149                 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    150                 mListener.onWindowCreated(holder);
    151             }
    152         }
    153 
    154         @Override
    155         public void dismiss() {
    156             if (mWindowVisible) {
    157                 mListener.onWindowDestroyed();
    158 
    159                 mWindowManager.removeView(mSurfaceView);
    160                 mWindowVisible = false;
    161             }
    162         }
    163 
    164         @Override
    165         public void updateAspectRatio(int width, int height) {
    166         }
    167     }
    168 
    169     /**
    170      * Implementation for API version 17+.
    171      */
    172     private static final class JellybeanMr1Impl extends OverlayDisplayWindow {
    173         // When true, disables support for moving and resizing the overlay.
    174         // The window is made non-touchable, which makes it possible to
    175         // directly interact with the content underneath.
    176         private static final boolean DISABLE_MOVE_AND_RESIZE = false;
    177 
    178         private final DisplayManager mDisplayManager;
    179         private final WindowManager mWindowManager;
    180 
    181         private final Display mDefaultDisplay;
    182         private final DisplayMetrics mDefaultDisplayMetrics = new DisplayMetrics();
    183 
    184         private View mWindowContent;
    185         private WindowManager.LayoutParams mWindowParams;
    186         private TextureView mTextureView;
    187         private TextView mNameTextView;
    188 
    189         private GestureDetector mGestureDetector;
    190         private ScaleGestureDetector mScaleGestureDetector;
    191 
    192         private boolean mWindowVisible;
    193         private int mWindowX;
    194         private int mWindowY;
    195         private float mWindowScale;
    196 
    197         private float mLiveTranslationX;
    198         private float mLiveTranslationY;
    199         private float mLiveScale = 1.0f;
    200 
    201         public JellybeanMr1Impl(Context context, String name,
    202                 int width, int height, int gravity) {
    203             super(context, name, width, height, gravity);
    204 
    205             mDisplayManager = (DisplayManager)context.getSystemService(
    206                     Context.DISPLAY_SERVICE);
    207             mWindowManager = (WindowManager)context.getSystemService(
    208                     Context.WINDOW_SERVICE);
    209 
    210             mDefaultDisplay = mWindowManager.getDefaultDisplay();
    211             updateDefaultDisplayInfo();
    212 
    213             createWindow();
    214         }
    215 
    216         @Override
    217         public void show() {
    218             if (!mWindowVisible) {
    219                 mDisplayManager.registerDisplayListener(mDisplayListener, null);
    220                 if (!updateDefaultDisplayInfo()) {
    221                     mDisplayManager.unregisterDisplayListener(mDisplayListener);
    222                     return;
    223                 }
    224 
    225                 clearLiveState();
    226                 updateWindowParams();
    227                 mWindowManager.addView(mWindowContent, mWindowParams);
    228                 mWindowVisible = true;
    229             }
    230         }
    231 
    232         @Override
    233         public void dismiss() {
    234             if (mWindowVisible) {
    235                 mDisplayManager.unregisterDisplayListener(mDisplayListener);
    236                 mWindowManager.removeView(mWindowContent);
    237                 mWindowVisible = false;
    238             }
    239         }
    240 
    241         @Override
    242         public void updateAspectRatio(int width, int height) {
    243             if (mWidth * height < mHeight * width) {
    244                 mTextureView.getLayoutParams().width = mWidth;
    245                 mTextureView.getLayoutParams().height = mWidth * height / width;
    246             } else {
    247                 mTextureView.getLayoutParams().width = mHeight * width / height;
    248                 mTextureView.getLayoutParams().height = mHeight;
    249             }
    250             relayout();
    251         }
    252 
    253         private void relayout() {
    254             if (mWindowVisible) {
    255                 updateWindowParams();
    256                 mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
    257             }
    258         }
    259 
    260         private boolean updateDefaultDisplayInfo() {
    261             mDefaultDisplay.getMetrics(mDefaultDisplayMetrics);
    262             return true;
    263         }
    264 
    265         private void createWindow() {
    266             LayoutInflater inflater = LayoutInflater.from(mContext);
    267 
    268             mWindowContent = inflater.inflate(
    269                     R.layout.overlay_display_window, null);
    270             mWindowContent.setOnTouchListener(mOnTouchListener);
    271 
    272             mTextureView = (TextureView)mWindowContent.findViewById(
    273                     R.id.overlay_display_window_texture);
    274             mTextureView.setPivotX(0);
    275             mTextureView.setPivotY(0);
    276             mTextureView.getLayoutParams().width = mWidth;
    277             mTextureView.getLayoutParams().height = mHeight;
    278             mTextureView.setOpaque(false);
    279             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    280 
    281             mNameTextView = (TextView)mWindowContent.findViewById(
    282                     R.id.overlay_display_window_title);
    283             mNameTextView.setText(mName);
    284 
    285             mWindowParams = new WindowManager.LayoutParams(
    286                     WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    287             mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    288                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    289                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    290                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    291                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    292             if (DISABLE_MOVE_AND_RESIZE) {
    293                 mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    294             }
    295             mWindowParams.alpha = WINDOW_ALPHA;
    296             mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
    297             mWindowParams.setTitle(mName);
    298 
    299             mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
    300             mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
    301 
    302             // Set the initial position and scale.
    303             // The position and scale will be clamped when the display is first shown.
    304             mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
    305                     0 : mDefaultDisplayMetrics.widthPixels;
    306             mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
    307                     0 : mDefaultDisplayMetrics.heightPixels;
    308             Log.d(TAG, mDefaultDisplayMetrics.toString());
    309             mWindowScale = INITIAL_SCALE;
    310 
    311             // calculate and save initial settings
    312             updateWindowParams();
    313             saveWindowParams();
    314         }
    315 
    316         private void updateWindowParams() {
    317             float scale = mWindowScale * mLiveScale;
    318             scale = Math.min(scale, (float)mDefaultDisplayMetrics.widthPixels / mWidth);
    319             scale = Math.min(scale, (float)mDefaultDisplayMetrics.heightPixels / mHeight);
    320             scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
    321 
    322             float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
    323             int width = (int)(mWidth * scale);
    324             int height = (int)(mHeight * scale);
    325             int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
    326             int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
    327             x = Math.max(0, Math.min(x, mDefaultDisplayMetrics.widthPixels - width));
    328             y = Math.max(0, Math.min(y, mDefaultDisplayMetrics.heightPixels - height));
    329 
    330             if (DEBUG) {
    331                 Log.d(TAG, "updateWindowParams: scale=" + scale
    332                         + ", offsetScale=" + offsetScale
    333                         + ", x=" + x + ", y=" + y
    334                         + ", width=" + width + ", height=" + height);
    335             }
    336 
    337             mTextureView.setScaleX(scale);
    338             mTextureView.setScaleY(scale);
    339 
    340             mTextureView.setTranslationX(
    341                     (mWidth - mTextureView.getLayoutParams().width) * scale / 2);
    342             mTextureView.setTranslationY(
    343                     (mHeight - mTextureView.getLayoutParams().height) * scale / 2);
    344 
    345             mWindowParams.x = x;
    346             mWindowParams.y = y;
    347             mWindowParams.width = width;
    348             mWindowParams.height = height;
    349         }
    350 
    351         private void saveWindowParams() {
    352             mWindowX = mWindowParams.x;
    353             mWindowY = mWindowParams.y;
    354             mWindowScale = mTextureView.getScaleX();
    355             clearLiveState();
    356         }
    357 
    358         private void clearLiveState() {
    359             mLiveTranslationX = 0f;
    360             mLiveTranslationY = 0f;
    361             mLiveScale = 1.0f;
    362         }
    363 
    364         private final DisplayManager.DisplayListener mDisplayListener =
    365                 new DisplayManager.DisplayListener() {
    366             @Override
    367             public void onDisplayAdded(int displayId) {
    368             }
    369 
    370             @Override
    371             public void onDisplayChanged(int displayId) {
    372                 if (displayId == mDefaultDisplay.getDisplayId()) {
    373                     if (updateDefaultDisplayInfo()) {
    374                         relayout();
    375                     } else {
    376                         dismiss();
    377                     }
    378                 }
    379             }
    380 
    381             @Override
    382             public void onDisplayRemoved(int displayId) {
    383                 if (displayId == mDefaultDisplay.getDisplayId()) {
    384                     dismiss();
    385                 }
    386             }
    387         };
    388 
    389         private final SurfaceTextureListener mSurfaceTextureListener =
    390                 new SurfaceTextureListener() {
    391             @Override
    392             public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
    393                     int width, int height) {
    394                 if (mListener != null) {
    395                     mListener.onWindowCreated(new Surface(surfaceTexture));
    396                 }
    397             }
    398 
    399             @Override
    400             public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    401                 if (mListener != null) {
    402                     mListener.onWindowDestroyed();
    403                 }
    404                 return true;
    405             }
    406 
    407             @Override
    408             public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
    409                     int width, int height) {
    410             }
    411 
    412             @Override
    413             public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    414             }
    415         };
    416 
    417         private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
    418             @Override
    419             public boolean onTouch(View view, MotionEvent event) {
    420                 // Work in screen coordinates.
    421                 final float oldX = event.getX();
    422                 final float oldY = event.getY();
    423                 event.setLocation(event.getRawX(), event.getRawY());
    424 
    425                 mGestureDetector.onTouchEvent(event);
    426                 mScaleGestureDetector.onTouchEvent(event);
    427 
    428                 switch (event.getActionMasked()) {
    429                     case MotionEvent.ACTION_UP:
    430                     case MotionEvent.ACTION_CANCEL:
    431                         saveWindowParams();
    432                         break;
    433                 }
    434 
    435                 // Revert to window coordinates.
    436                 event.setLocation(oldX, oldY);
    437                 return true;
    438             }
    439         };
    440 
    441         private final GestureDetector.OnGestureListener mOnGestureListener =
    442                 new GestureDetector.SimpleOnGestureListener() {
    443             @Override
    444             public boolean onScroll(MotionEvent e1, MotionEvent e2,
    445                     float distanceX, float distanceY) {
    446                 mLiveTranslationX -= distanceX;
    447                 mLiveTranslationY -= distanceY;
    448                 relayout();
    449                 return true;
    450             }
    451         };
    452 
    453         private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
    454                 new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    455             @Override
    456             public boolean onScale(ScaleGestureDetector detector) {
    457                 mLiveScale *= detector.getScaleFactor();
    458                 relayout();
    459                 return true;
    460             }
    461         };
    462     }
    463 }
    464