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