Merge "Add support lib version meta-data to AndroidManifest.xml" into nyc-support-25.1-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 4caef90..2811ea9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
[Hook Scripts]
-checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
[Builtin Hooks]
commit_msg_changeid_field = true
diff --git a/annotations/src/android/support/annotation/IntDef.java b/annotations/src/android/support/annotation/IntDef.java
index be2e2b8..f621b7f 100644
--- a/annotations/src/android/support/annotation/IntDef.java
+++ b/annotations/src/android/support/annotation/IntDef.java
@@ -15,16 +15,12 @@
*/
package android.support.annotation;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
/**
* Denotes that the annotated element of integer type, represents
* a logical type and that its value should be one of the explicitly
@@ -47,7 +43,7 @@
* For a flag, set the flag attribute:
* <pre><code>
* @IntDef(
- * flag = true
+ * flag = true,
* value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
* </code></pre>
*/
diff --git a/api/25.1.0.txt b/api/25.1.0.txt
index 38141fe..0b2129f 100644
--- a/api/25.1.0.txt
+++ b/api/25.1.0.txt
@@ -858,6 +858,173 @@
}
+package android.support.media {
+
+ public class ExifInterface {
+ ctor public ExifInterface(java.lang.String) throws java.io.IOException;
+ ctor public ExifInterface(java.io.InputStream) throws java.io.IOException;
+ method public double getAltitude(double);
+ method public java.lang.String getAttribute(java.lang.String);
+ method public double getAttributeDouble(java.lang.String, double);
+ method public int getAttributeInt(java.lang.String, int);
+ method public boolean getLatLong(float[]);
+ method public byte[] getThumbnail();
+ method public android.graphics.Bitmap getThumbnailBitmap();
+ method public byte[] getThumbnailBytes();
+ method public long[] getThumbnailRange();
+ method public boolean hasThumbnail();
+ method public boolean isThumbnailCompressed();
+ method public void saveAttributes() throws java.io.IOException;
+ method public void setAttribute(java.lang.String, java.lang.String);
+ field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
+ field public static final int ORIENTATION_FLIP_VERTICAL = 4; // 0x4
+ field public static final int ORIENTATION_NORMAL = 1; // 0x1
+ field public static final int ORIENTATION_ROTATE_180 = 3; // 0x3
+ field public static final int ORIENTATION_ROTATE_270 = 8; // 0x8
+ field public static final int ORIENTATION_ROTATE_90 = 6; // 0x6
+ field public static final int ORIENTATION_TRANSPOSE = 5; // 0x5
+ field public static final int ORIENTATION_TRANSVERSE = 7; // 0x7
+ field public static final int ORIENTATION_UNDEFINED = 0; // 0x0
+ field public static final java.lang.String TAG_APERTURE_VALUE = "ApertureValue";
+ field public static final java.lang.String TAG_ARTIST = "Artist";
+ field public static final java.lang.String TAG_BITS_PER_SAMPLE = "BitsPerSample";
+ field public static final java.lang.String TAG_BRIGHTNESS_VALUE = "BrightnessValue";
+ field public static final java.lang.String TAG_CFA_PATTERN = "CFAPattern";
+ field public static final java.lang.String TAG_COLOR_SPACE = "ColorSpace";
+ field public static final java.lang.String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration";
+ field public static final java.lang.String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel";
+ field public static final java.lang.String TAG_COMPRESSION = "Compression";
+ field public static final java.lang.String TAG_CONTRAST = "Contrast";
+ field public static final java.lang.String TAG_COPYRIGHT = "Copyright";
+ field public static final java.lang.String TAG_CUSTOM_RENDERED = "CustomRendered";
+ field public static final java.lang.String TAG_DATETIME = "DateTime";
+ field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ field public static final java.lang.String TAG_DATETIME_ORIGINAL = "DateTimeOriginal";
+ field public static final java.lang.String TAG_DEFAULT_CROP_SIZE = "DefaultCropSize";
+ field public static final java.lang.String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription";
+ field public static final java.lang.String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
+ field public static final java.lang.String TAG_DNG_VERSION = "DNGVersion";
+ field public static final java.lang.String TAG_EXIF_VERSION = "ExifVersion";
+ field public static final java.lang.String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
+ field public static final java.lang.String TAG_EXPOSURE_INDEX = "ExposureIndex";
+ field public static final java.lang.String TAG_EXPOSURE_MODE = "ExposureMode";
+ field public static final java.lang.String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
+ field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime";
+ field public static final java.lang.String TAG_FILE_SOURCE = "FileSource";
+ field public static final java.lang.String TAG_FLASH = "Flash";
+ field public static final java.lang.String TAG_FLASHPIX_VERSION = "FlashpixVersion";
+ field public static final java.lang.String TAG_FLASH_ENERGY = "FlashEnergy";
+ field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength";
+ field public static final java.lang.String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm";
+ field public static final java.lang.String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit";
+ field public static final java.lang.String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution";
+ field public static final java.lang.String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution";
+ field public static final java.lang.String TAG_F_NUMBER = "FNumber";
+ field public static final java.lang.String TAG_GAIN_CONTROL = "GainControl";
+ field public static final java.lang.String TAG_GPS_ALTITUDE = "GPSAltitude";
+ field public static final java.lang.String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
+ field public static final java.lang.String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation";
+ field public static final java.lang.String TAG_GPS_DATESTAMP = "GPSDateStamp";
+ field public static final java.lang.String TAG_GPS_DEST_BEARING = "GPSDestBearing";
+ field public static final java.lang.String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef";
+ field public static final java.lang.String TAG_GPS_DEST_DISTANCE = "GPSDestDistance";
+ field public static final java.lang.String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef";
+ field public static final java.lang.String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude";
+ field public static final java.lang.String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef";
+ field public static final java.lang.String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude";
+ field public static final java.lang.String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef";
+ field public static final java.lang.String TAG_GPS_DIFFERENTIAL = "GPSDifferential";
+ field public static final java.lang.String TAG_GPS_DOP = "GPSDOP";
+ field public static final java.lang.String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
+ field public static final java.lang.String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
+ field public static final java.lang.String TAG_GPS_LATITUDE = "GPSLatitude";
+ field public static final java.lang.String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+ field public static final java.lang.String TAG_GPS_LONGITUDE = "GPSLongitude";
+ field public static final java.lang.String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+ field public static final java.lang.String TAG_GPS_MAP_DATUM = "GPSMapDatum";
+ field public static final java.lang.String TAG_GPS_MEASURE_MODE = "GPSMeasureMode";
+ field public static final java.lang.String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
+ field public static final java.lang.String TAG_GPS_SATELLITES = "GPSSatellites";
+ field public static final java.lang.String TAG_GPS_SPEED = "GPSSpeed";
+ field public static final java.lang.String TAG_GPS_SPEED_REF = "GPSSpeedRef";
+ field public static final java.lang.String TAG_GPS_STATUS = "GPSStatus";
+ field public static final java.lang.String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+ field public static final java.lang.String TAG_GPS_TRACK = "GPSTrack";
+ field public static final java.lang.String TAG_GPS_TRACK_REF = "GPSTrackRef";
+ field public static final java.lang.String TAG_GPS_VERSION_ID = "GPSVersionID";
+ field public static final java.lang.String TAG_IMAGE_DESCRIPTION = "ImageDescription";
+ field public static final java.lang.String TAG_IMAGE_LENGTH = "ImageLength";
+ field public static final java.lang.String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID";
+ field public static final java.lang.String TAG_IMAGE_WIDTH = "ImageWidth";
+ field public static final java.lang.String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex";
+ field public static final java.lang.String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings";
+ field public static final java.lang.String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat";
+ field public static final java.lang.String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength";
+ field public static final java.lang.String TAG_LIGHT_SOURCE = "LightSource";
+ field public static final java.lang.String TAG_MAKE = "Make";
+ field public static final java.lang.String TAG_MAKER_NOTE = "MakerNote";
+ field public static final java.lang.String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
+ field public static final java.lang.String TAG_METERING_MODE = "MeteringMode";
+ field public static final java.lang.String TAG_MODEL = "Model";
+ field public static final java.lang.String TAG_NEW_SUBFILE_TYPE = "NewSubfileType";
+ field public static final java.lang.String TAG_OECF = "OECF";
+ field public static final java.lang.String TAG_ORF_ASPECT_FRAME = "AspectFrame";
+ field public static final java.lang.String TAG_ORF_PREVIEW_IMAGE_LENGTH = "PreviewImageLength";
+ field public static final java.lang.String TAG_ORF_PREVIEW_IMAGE_START = "PreviewImageStart";
+ field public static final java.lang.String TAG_ORF_THUMBNAIL_IMAGE = "ThumbnailImage";
+ field public static final java.lang.String TAG_ORIENTATION = "Orientation";
+ field public static final java.lang.String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+ field public static final java.lang.String TAG_PIXEL_X_DIMENSION = "PixelXDimension";
+ field public static final java.lang.String TAG_PIXEL_Y_DIMENSION = "PixelYDimension";
+ field public static final java.lang.String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration";
+ field public static final java.lang.String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities";
+ field public static final java.lang.String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite";
+ field public static final java.lang.String TAG_RELATED_SOUND_FILE = "RelatedSoundFile";
+ field public static final java.lang.String TAG_RESOLUTION_UNIT = "ResolutionUnit";
+ field public static final java.lang.String TAG_ROWS_PER_STRIP = "RowsPerStrip";
+ field public static final java.lang.String TAG_RW2_ISO = "ISO";
+ field public static final java.lang.String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw";
+ field public static final java.lang.String TAG_RW2_SENSOR_BOTTOM_BORDER = "SensorBottomBorder";
+ field public static final java.lang.String TAG_RW2_SENSOR_LEFT_BORDER = "SensorLeftBorder";
+ field public static final java.lang.String TAG_RW2_SENSOR_RIGHT_BORDER = "SensorRightBorder";
+ field public static final java.lang.String TAG_RW2_SENSOR_TOP_BORDER = "SensorTopBorder";
+ field public static final java.lang.String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel";
+ field public static final java.lang.String TAG_SATURATION = "Saturation";
+ field public static final java.lang.String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType";
+ field public static final java.lang.String TAG_SCENE_TYPE = "SceneType";
+ field public static final java.lang.String TAG_SENSING_METHOD = "SensingMethod";
+ field public static final java.lang.String TAG_SHARPNESS = "Sharpness";
+ field public static final java.lang.String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue";
+ field public static final java.lang.String TAG_SOFTWARE = "Software";
+ field public static final java.lang.String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
+ field public static final java.lang.String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
+ field public static final java.lang.String TAG_STRIP_BYTE_COUNTS = "StripByteCounts";
+ field public static final java.lang.String TAG_STRIP_OFFSETS = "StripOffsets";
+ field public static final java.lang.String TAG_SUBFILE_TYPE = "SubfileType";
+ field public static final java.lang.String TAG_SUBJECT_AREA = "SubjectArea";
+ field public static final java.lang.String TAG_SUBJECT_DISTANCE = "SubjectDistance";
+ field public static final java.lang.String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange";
+ field public static final java.lang.String TAG_SUBJECT_LOCATION = "SubjectLocation";
+ field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime";
+ field public static final java.lang.String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized";
+ field public static final java.lang.String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal";
+ field public static final java.lang.String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
+ field public static final java.lang.String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
+ field public static final java.lang.String TAG_TRANSFER_FUNCTION = "TransferFunction";
+ field public static final java.lang.String TAG_USER_COMMENT = "UserComment";
+ field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance";
+ field public static final java.lang.String TAG_WHITE_POINT = "WhitePoint";
+ field public static final java.lang.String TAG_X_RESOLUTION = "XResolution";
+ field public static final java.lang.String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
+ field public static final java.lang.String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
+ field public static final java.lang.String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling";
+ field public static final java.lang.String TAG_Y_RESOLUTION = "YResolution";
+ field public static final int WHITEBALANCE_AUTO = 0; // 0x0
+ field public static final int WHITEBALANCE_MANUAL = 1; // 0x1
+ }
+
+}
+
package android.support.percent {
public class PercentFrameLayout extends android.widget.FrameLayout {
@@ -1761,10 +1928,9 @@
method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
- public abstract class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
- ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
- ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
- ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+ public abstract deprecated class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
+ ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+ ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
method public void detach();
method public int getCurrentPosition();
@@ -1815,59 +1981,25 @@
method public final void setLogoResourceId(int);
}
- public abstract class PlaybackControlGlue extends android.support.v17.leanback.app.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+ public abstract deprecated class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
ctor public PlaybackControlGlue(android.content.Context, int[]);
ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
- ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
- ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
- ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+ ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+ ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter createControlsRowAndPresenter();
method protected android.support.v17.leanback.widget.SparseArrayObjectAdapter createPrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector);
- method public void enableProgressUpdating(boolean);
- method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
- method public abstract int getCurrentPosition();
- method public abstract int getCurrentSpeedId();
- method public int[] getFastForwardSpeeds();
- method public deprecated android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
- method public abstract android.graphics.drawable.Drawable getMediaArt();
- method public abstract int getMediaDuration();
- method public abstract java.lang.CharSequence getMediaSubtitle();
- method public abstract java.lang.CharSequence getMediaTitle();
+ method public android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
method public deprecated android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public int[] getRewindSpeeds();
- method public abstract long getSupportedActions();
- method public int getUpdatePeriod();
- method public abstract boolean hasValidMedia();
- method public boolean isFadingEnabled();
- method public abstract boolean isMediaPlaying();
- method public void onActionClicked(android.support.v17.leanback.widget.Action);
- method public boolean onKey(android.view.View, int, android.view.KeyEvent);
- method protected void onMetadataChanged();
- method protected deprecated void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
- method protected void onStateChanged();
+ method public final void next();
+ method protected void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method public final void pause();
method protected deprecated void pausePlayback();
- method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
- method public void setFadingEnabled(boolean);
+ method public final void play(int);
+ method public final void previous();
method public deprecated void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
method protected deprecated void skipToNext();
method protected deprecated void skipToPrevious();
method protected deprecated void startPlayback(int);
- method public void updateProgress();
- field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
- field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
- field public static final int ACTION_FAST_FORWARD = 128; // 0x80
- field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
- field public static final int ACTION_REWIND = 32; // 0x20
- field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
- field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
- field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
- field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
- field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
- field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
- field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
- field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
- field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
- field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
}
public static abstract deprecated interface PlaybackControlGlue.InputEventHandler {
@@ -1901,16 +2033,12 @@
method public void fadeOut();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public int getBackgroundType();
- method public final android.view.View.OnKeyListener getEventHandler();
- method public android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener getFadeCompleteListener();
method public boolean isFadingEnabled();
method public void notifyPlaybackRowChanged();
- method public void resetFocus();
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setBackgroundType(int);
- method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener);
method public void setFadingEnabled(boolean);
- method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+ method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -1924,52 +2052,10 @@
field public static final int BG_NONE = 0; // 0x0
}
- public static class PlaybackFragment.OnFadeCompleteListener {
- ctor public PlaybackFragment.OnFadeCompleteListener();
- method public void onFadeInComplete();
- method public void onFadeOutComplete();
- }
-
- public class PlaybackFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+ public class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
}
- public abstract class PlaybackGlue {
- ctor public PlaybackGlue(android.content.Context);
- method public android.content.Context getContext();
- method public android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost getHost();
- method public boolean isReadyForPlayback();
- method public void next();
- method public void onDetachedFromHost();
- method public void pause();
- method public void play();
- method public void previous();
- method public void setHost(android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost);
- method public void setPlayerCallback(android.support.v17.leanback.app.PlaybackGlue.PlayerCallback);
- }
-
- public static abstract class PlaybackGlue.HostLifecycleCallback {
- ctor public PlaybackGlue.HostLifecycleCallback();
- method public abstract void onHostStart();
- method public abstract void onHostStop();
- }
-
- public static class PlaybackGlue.PlaybackGlueHost {
- ctor public PlaybackGlue.PlaybackGlueHost();
- method public void notifyPlaybackRowChanged();
- method public void setFadingEnabled(boolean);
- method public void setHostLifeCycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
- method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
- method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
- method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
- method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
- }
-
- public static abstract class PlaybackGlue.PlayerCallback {
- ctor public PlaybackGlue.PlayerCallback();
- method public abstract void onReadyForPlayback();
- }
-
public deprecated class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
ctor public PlaybackOverlayFragment();
method public void fadeOut();
@@ -2031,16 +2117,12 @@
method public void fadeOut();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public int getBackgroundType();
- method public final android.view.View.OnKeyListener getEventHandler();
- method public android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener getFadeCompleteListener();
method public boolean isFadingEnabled();
method public void notifyPlaybackRowChanged();
- method public void resetFocus();
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setBackgroundType(int);
- method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener);
method public void setFadingEnabled(boolean);
- method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+ method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -2054,13 +2136,7 @@
field public static final int BG_NONE = 0; // 0x0
}
- public static class PlaybackSupportFragment.OnFadeCompleteListener {
- ctor public PlaybackSupportFragment.OnFadeCompleteListener();
- method public void onFadeInComplete();
- method public void onFadeOutComplete();
- }
-
- public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+ public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
}
@@ -2186,10 +2262,6 @@
method public abstract boolean onQueryTextSubmit(java.lang.String);
}
- public abstract interface SurfaceHolderGlueHost {
- method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
- }
-
public class VerticalGridFragment extends android.support.v17.leanback.app.BrandedFragment {
ctor public VerticalGridFragment();
method protected java.lang.Object createEntranceTransition();
@@ -2261,6 +2333,123 @@
}
+package android.support.v17.leanback.media {
+
+ public abstract class MediaControllerGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+ ctor public MediaControllerGlue(android.content.Context, int[], int[]);
+ method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
+ method public void detach();
+ method public int getCurrentPosition();
+ method public int getCurrentSpeedId();
+ method public android.graphics.drawable.Drawable getMediaArt();
+ method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
+ method public int getMediaDuration();
+ method public java.lang.CharSequence getMediaSubtitle();
+ method public java.lang.CharSequence getMediaTitle();
+ method public long getSupportedActions();
+ method public boolean hasValidMedia();
+ method public boolean isMediaPlaying();
+ }
+
+ public abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+ ctor public PlaybackControlGlue(android.content.Context, int[]);
+ ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
+ method public void enableProgressUpdating(boolean);
+ method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+ method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter getControlsRowPresenter();
+ method public abstract int getCurrentPosition();
+ method public abstract int getCurrentSpeedId();
+ method public int[] getFastForwardSpeeds();
+ method public abstract android.graphics.drawable.Drawable getMediaArt();
+ method public abstract int getMediaDuration();
+ method public abstract java.lang.CharSequence getMediaSubtitle();
+ method public abstract java.lang.CharSequence getMediaTitle();
+ method public int[] getRewindSpeeds();
+ method public abstract long getSupportedActions();
+ method public int getUpdatePeriod();
+ method public abstract boolean hasValidMedia();
+ method public boolean isFadingEnabled();
+ method public abstract boolean isMediaPlaying();
+ method public void onActionClicked(android.support.v17.leanback.widget.Action);
+ method protected void onCreateControlsRowAndPresenter();
+ method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.SparseArrayObjectAdapter);
+ method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+ method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+ method protected void onMetadataChanged();
+ method protected void onStateChanged();
+ method public void play(int);
+ method public final void play();
+ method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method public void setControlsRowPresenter(android.support.v17.leanback.widget.PlaybackControlsRowPresenter);
+ method public void setFadingEnabled(boolean);
+ method public void updateProgress();
+ field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+ field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+ field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+ field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+ field public static final int ACTION_REWIND = 32; // 0x20
+ field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+ field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+ field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+ field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+ field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+ field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+ field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+ field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+ field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+ field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+ }
+
+ public abstract class PlaybackGlue {
+ ctor public PlaybackGlue(android.content.Context);
+ method public android.content.Context getContext();
+ method public android.support.v17.leanback.media.PlaybackGlueHost getHost();
+ method public boolean isReadyForPlayback();
+ method public void next();
+ method protected void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
+ method protected void onDetachedFromHost();
+ method protected void onHostPause();
+ method protected void onHostResume();
+ method protected void onHostStart();
+ method protected void onHostStop();
+ method public void pause();
+ method public void play();
+ method public void previous();
+ method public final void setHost(android.support.v17.leanback.media.PlaybackGlueHost);
+ method public void setPlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
+ }
+
+ public static abstract class PlaybackGlue.PlayerCallback {
+ ctor public PlaybackGlue.PlayerCallback();
+ method public abstract void onReadyForPlayback();
+ }
+
+ public abstract class PlaybackGlueHost {
+ ctor public PlaybackGlueHost();
+ method public void fadeOut();
+ method public void notifyPlaybackRowChanged();
+ method public void setFadingEnabled(boolean);
+ method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+ method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+ method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+ method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+ method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+ }
+
+ public static abstract class PlaybackGlueHost.HostCallback {
+ ctor public PlaybackGlueHost.HostCallback();
+ method public void onHostPause();
+ method public void onHostResume();
+ method public void onHostStart();
+ method public void onHostStop();
+ }
+
+ public abstract interface SurfaceHolderGlueHost {
+ method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+ }
+
+}
+
package android.support.v17.leanback.system {
public class Settings {
@@ -9574,6 +9763,7 @@
method public int findLastVisibleItemPosition();
method public android.support.v7.widget.RecyclerView.LayoutParams generateDefaultLayoutParams();
method protected int getExtraLayoutSpace(android.support.v7.widget.RecyclerView.State);
+ method public int getInitialItemPrefetchCount();
method public int getOrientation();
method public boolean getRecycleChildrenOnDetach();
method public boolean getReverseLayout();
@@ -9581,6 +9771,7 @@
method protected boolean isLayoutRTL();
method public boolean isSmoothScrollbarEnabled();
method public void scrollToPositionWithOffset(int, int);
+ method public void setInitialPrefetchItemCount(int);
method public void setOrientation(int);
method public void setRecycleChildrenOnDetach(boolean);
method public void setReverseLayout(boolean);
@@ -9971,6 +10162,8 @@
method public boolean canScrollVertically();
method public boolean checkLayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
method public static int chooseSize(int, int, int);
+ method public void collectAdjacentPrefetchPositions(int, int, android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry);
+ method public void collectInitialPrefetchPositions(int, android.support.v7.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry);
method public int computeHorizontalScrollExtent(android.support.v7.widget.RecyclerView.State);
method public int computeHorizontalScrollOffset(android.support.v7.widget.RecyclerView.State);
method public int computeHorizontalScrollRange(android.support.v7.widget.RecyclerView.State);
@@ -10096,6 +10289,10 @@
method public boolean supportsPredictiveItemAnimations();
}
+ public static abstract interface RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+ method public abstract void addPosition(int, int);
+ }
+
public static class RecyclerView.LayoutManager.Properties {
ctor public RecyclerView.LayoutManager.Properties();
field public int orientation;
diff --git a/api/current.txt b/api/current.txt
index fb6b8b8..0b2129f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1928,10 +1928,9 @@
method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
- public abstract class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
- ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
- ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
- ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+ public abstract deprecated class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
+ ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+ ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
method public void detach();
method public int getCurrentPosition();
@@ -1982,59 +1981,25 @@
method public final void setLogoResourceId(int);
}
- public abstract class PlaybackControlGlue extends android.support.v17.leanback.app.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+ public abstract deprecated class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
ctor public PlaybackControlGlue(android.content.Context, int[]);
ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
- ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
- ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
- ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+ ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+ ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter createControlsRowAndPresenter();
method protected android.support.v17.leanback.widget.SparseArrayObjectAdapter createPrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector);
- method public void enableProgressUpdating(boolean);
- method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
- method public abstract int getCurrentPosition();
- method public abstract int getCurrentSpeedId();
- method public int[] getFastForwardSpeeds();
- method public deprecated android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
- method public abstract android.graphics.drawable.Drawable getMediaArt();
- method public abstract int getMediaDuration();
- method public abstract java.lang.CharSequence getMediaSubtitle();
- method public abstract java.lang.CharSequence getMediaTitle();
+ method public android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
method public deprecated android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public int[] getRewindSpeeds();
- method public abstract long getSupportedActions();
- method public int getUpdatePeriod();
- method public abstract boolean hasValidMedia();
- method public boolean isFadingEnabled();
- method public abstract boolean isMediaPlaying();
- method public void onActionClicked(android.support.v17.leanback.widget.Action);
- method public boolean onKey(android.view.View, int, android.view.KeyEvent);
- method protected void onMetadataChanged();
- method protected deprecated void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
- method protected void onStateChanged();
+ method public final void next();
+ method protected void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method public final void pause();
method protected deprecated void pausePlayback();
- method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
- method public void setFadingEnabled(boolean);
+ method public final void play(int);
+ method public final void previous();
method public deprecated void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
method protected deprecated void skipToNext();
method protected deprecated void skipToPrevious();
method protected deprecated void startPlayback(int);
- method public void updateProgress();
- field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
- field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
- field public static final int ACTION_FAST_FORWARD = 128; // 0x80
- field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
- field public static final int ACTION_REWIND = 32; // 0x20
- field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
- field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
- field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
- field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
- field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
- field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
- field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
- field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
- field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
- field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
}
public static abstract deprecated interface PlaybackControlGlue.InputEventHandler {
@@ -2068,16 +2033,12 @@
method public void fadeOut();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public int getBackgroundType();
- method public final android.view.View.OnKeyListener getEventHandler();
- method public android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener getFadeCompleteListener();
method public boolean isFadingEnabled();
method public void notifyPlaybackRowChanged();
- method public void resetFocus();
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setBackgroundType(int);
- method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener);
method public void setFadingEnabled(boolean);
- method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+ method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -2091,52 +2052,10 @@
field public static final int BG_NONE = 0; // 0x0
}
- public static class PlaybackFragment.OnFadeCompleteListener {
- ctor public PlaybackFragment.OnFadeCompleteListener();
- method public void onFadeInComplete();
- method public void onFadeOutComplete();
- }
-
- public class PlaybackFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+ public class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
}
- public abstract class PlaybackGlue {
- ctor public PlaybackGlue(android.content.Context);
- method public android.content.Context getContext();
- method public android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost getHost();
- method public boolean isReadyForPlayback();
- method public void next();
- method public void onDetachedFromHost();
- method public void pause();
- method public void play();
- method public void previous();
- method public void setHost(android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost);
- method public void setPlayerCallback(android.support.v17.leanback.app.PlaybackGlue.PlayerCallback);
- }
-
- public static abstract class PlaybackGlue.HostLifecycleCallback {
- ctor public PlaybackGlue.HostLifecycleCallback();
- method public abstract void onHostStart();
- method public abstract void onHostStop();
- }
-
- public static class PlaybackGlue.PlaybackGlueHost {
- ctor public PlaybackGlue.PlaybackGlueHost();
- method public void notifyPlaybackRowChanged();
- method public void setFadingEnabled(boolean);
- method public void setHostLifeCycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
- method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
- method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
- method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
- method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
- }
-
- public static abstract class PlaybackGlue.PlayerCallback {
- ctor public PlaybackGlue.PlayerCallback();
- method public abstract void onReadyForPlayback();
- }
-
public deprecated class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
ctor public PlaybackOverlayFragment();
method public void fadeOut();
@@ -2198,16 +2117,12 @@
method public void fadeOut();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public int getBackgroundType();
- method public final android.view.View.OnKeyListener getEventHandler();
- method public android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener getFadeCompleteListener();
method public boolean isFadingEnabled();
method public void notifyPlaybackRowChanged();
- method public void resetFocus();
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setBackgroundType(int);
- method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener);
method public void setFadingEnabled(boolean);
- method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+ method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -2221,13 +2136,7 @@
field public static final int BG_NONE = 0; // 0x0
}
- public static class PlaybackSupportFragment.OnFadeCompleteListener {
- ctor public PlaybackSupportFragment.OnFadeCompleteListener();
- method public void onFadeInComplete();
- method public void onFadeOutComplete();
- }
-
- public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+ public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
}
@@ -2353,10 +2262,6 @@
method public abstract boolean onQueryTextSubmit(java.lang.String);
}
- public abstract interface SurfaceHolderGlueHost {
- method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
- }
-
public class VerticalGridFragment extends android.support.v17.leanback.app.BrandedFragment {
ctor public VerticalGridFragment();
method protected java.lang.Object createEntranceTransition();
@@ -2428,6 +2333,123 @@
}
+package android.support.v17.leanback.media {
+
+ public abstract class MediaControllerGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+ ctor public MediaControllerGlue(android.content.Context, int[], int[]);
+ method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
+ method public void detach();
+ method public int getCurrentPosition();
+ method public int getCurrentSpeedId();
+ method public android.graphics.drawable.Drawable getMediaArt();
+ method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
+ method public int getMediaDuration();
+ method public java.lang.CharSequence getMediaSubtitle();
+ method public java.lang.CharSequence getMediaTitle();
+ method public long getSupportedActions();
+ method public boolean hasValidMedia();
+ method public boolean isMediaPlaying();
+ }
+
+ public abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+ ctor public PlaybackControlGlue(android.content.Context, int[]);
+ ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
+ method public void enableProgressUpdating(boolean);
+ method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+ method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter getControlsRowPresenter();
+ method public abstract int getCurrentPosition();
+ method public abstract int getCurrentSpeedId();
+ method public int[] getFastForwardSpeeds();
+ method public abstract android.graphics.drawable.Drawable getMediaArt();
+ method public abstract int getMediaDuration();
+ method public abstract java.lang.CharSequence getMediaSubtitle();
+ method public abstract java.lang.CharSequence getMediaTitle();
+ method public int[] getRewindSpeeds();
+ method public abstract long getSupportedActions();
+ method public int getUpdatePeriod();
+ method public abstract boolean hasValidMedia();
+ method public boolean isFadingEnabled();
+ method public abstract boolean isMediaPlaying();
+ method public void onActionClicked(android.support.v17.leanback.widget.Action);
+ method protected void onCreateControlsRowAndPresenter();
+ method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.SparseArrayObjectAdapter);
+ method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+ method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+ method protected void onMetadataChanged();
+ method protected void onStateChanged();
+ method public void play(int);
+ method public final void play();
+ method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method public void setControlsRowPresenter(android.support.v17.leanback.widget.PlaybackControlsRowPresenter);
+ method public void setFadingEnabled(boolean);
+ method public void updateProgress();
+ field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+ field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+ field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+ field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+ field public static final int ACTION_REWIND = 32; // 0x20
+ field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+ field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+ field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+ field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+ field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+ field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+ field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+ field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+ field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+ field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+ }
+
+ public abstract class PlaybackGlue {
+ ctor public PlaybackGlue(android.content.Context);
+ method public android.content.Context getContext();
+ method public android.support.v17.leanback.media.PlaybackGlueHost getHost();
+ method public boolean isReadyForPlayback();
+ method public void next();
+ method protected void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
+ method protected void onDetachedFromHost();
+ method protected void onHostPause();
+ method protected void onHostResume();
+ method protected void onHostStart();
+ method protected void onHostStop();
+ method public void pause();
+ method public void play();
+ method public void previous();
+ method public final void setHost(android.support.v17.leanback.media.PlaybackGlueHost);
+ method public void setPlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
+ }
+
+ public static abstract class PlaybackGlue.PlayerCallback {
+ ctor public PlaybackGlue.PlayerCallback();
+ method public abstract void onReadyForPlayback();
+ }
+
+ public abstract class PlaybackGlueHost {
+ ctor public PlaybackGlueHost();
+ method public void fadeOut();
+ method public void notifyPlaybackRowChanged();
+ method public void setFadingEnabled(boolean);
+ method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+ method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+ method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+ method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+ method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+ }
+
+ public static abstract class PlaybackGlueHost.HostCallback {
+ ctor public PlaybackGlueHost.HostCallback();
+ method public void onHostPause();
+ method public void onHostResume();
+ method public void onHostStart();
+ method public void onHostStop();
+ }
+
+ public abstract interface SurfaceHolderGlueHost {
+ method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+ }
+
+}
+
package android.support.v17.leanback.system {
public class Settings {
diff --git a/build.gradle b/build.gradle
index e91165c..475c555 100644
--- a/build.gradle
+++ b/build.gradle
@@ -41,8 +41,8 @@
}
gradle.ext.currentSdk = 25
-ext.supportVersion = '25.1.0-SNAPSHOT'
-ext.extraVersion = 40
+ext.supportVersion = '25.1.1'
+ext.extraVersion = 42
ext.supportRepoOut = ''
ext.buildToolsVersion = '24.0.1'
ext.buildNumber = Integer.toString(ext.extraVersion)
@@ -57,7 +57,8 @@
// Use the embedded SDK by default, which can be overridden by setting the 'sdk.dir' property
// (e.g. local.properties) or the 'ANDROID_HOME' environment variable.
final String platform = OperatingSystem.current().isMacOsX() ? 'darwin' : 'linux'
-System.setProperty('android.home', "${rootDir}/../../prebuilts/fullsdk-${platform}")
+ext.sdkDirectory = "${rootDir}/../../prebuilts/fullsdk-${platform}";
+System.setProperty('android.home', sdkDirectory)
/*
* With the build server you are given two env variables.
@@ -190,7 +191,7 @@
* @return a file collection containing the Android prebuilt JAR
*/
FileCollection getAndroidPrebuilt(apiLevel) {
- files("${project.rootDir}/../../prebuilts/sdk/$apiLevel/android.jar")
+ files("${ext.sdkDirectory}/platforms/android-${apiLevel}/android.jar")
}
void registerForDocsTask(Task task, Project subProject, releaseVariant) {
@@ -230,8 +231,6 @@
addStringOption "templatedir",
"${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
addStringOption "federate Android", "http://developer.android.com"
- addStringOption "federationapi Android",
- "${project.rootDir}/../../prebuilts/sdk/api/24.txt"
addStringOption "stubpackages", "android.support.*"
addStringOption "samplesdir", "${project.rootDir}/samples"
addOption hdfOption
@@ -256,8 +255,6 @@
addStringOption "templatedir",
"${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
addStringOption "federate Android", "http://developer.android.com"
- addStringOption "federationapi Android",
- "${project.rootDir}/../../prebuilts/sdk/api/24.txt"
addStringOption "stubpackages", "android.support.*"
}
exclude '**/BuildConfig.java'
@@ -321,10 +318,10 @@
checkApi.dependsOn checkApiStable
subprojects {
- // Only modify android projects.
+ // Only modify Android projects.
if (project.name.equals('doclava')) return;
- // current SDK is set in studioCompat.gradle
+ // Current SDK is set in studioCompat.gradle.
project.ext.currentSdk = gradle.ext.currentSdk
apply plugin: 'maven'
@@ -338,23 +335,29 @@
}
project.plugins.whenPluginAdded { plugin ->
- if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
- || "com.android.build.gradle.AppPlugin".equals(plugin.class.name)) {
+ def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
+ def isAndroidApp = "com.android.build.gradle.AppPlugin".equals(plugin.class.name)
+ def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
+
+ if (isAndroidLibrary || isAndroidApp) {
project.android.buildToolsVersion = rootProject.buildToolsVersion
- // enable code coverage for debug builds only if we are not running inside the IDE
- // enabling coverage reports breaks the method parameter resolution in the IDE debugger
+
+ // Enable code coverage for debug builds only if we are not running inside the IDE,
+ // since enabling coverage reports breaks the method parameter resolution in the IDE
+ // debugger.
project.android.buildTypes.debug.testCoverageEnabled = !hasProperty('android.injected.invoked.from.ide')
- // Ensure we run API lint checks.
+ // Enforce NewApi lint check as fatal.
project.android.lintOptions.check 'NewApi'
project.android.lintOptions.fatal 'NewApi'
project.parent.lint.dependsOn project.lint
}
- // Create release and separate zip task for Android libraries (and android-annotations,
- // which is just a Java library).
- if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
- || "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)) {
+ if (isAndroidLibrary || isJavaLibrary) {
+ // Add library to the aggregate dependency report.
+ task allDeps(type: DependencyReportTask) {}
+
+ // Create release and separate zip task for library.
task release(type: Upload) {
configuration = configurations.archives
repositories {
@@ -421,9 +424,10 @@
}
project.parent.createArchive.dependsOn createSeparateZip
- // before the upload, make sure the repo is ready.
+ // Before the upload, make sure the repo is ready.
release.dependsOn rootProject.tasks.prepareRepo
- // make the mainupload depend on this one.
+
+ // Make the mainupload depend on this one.
mainUpload.dependsOn release
}
}
diff --git a/core-ui/java/android/support/v4/widget/DrawerLayout.java b/core-ui/java/android/support/v4/widget/DrawerLayout.java
index 7a564bb..f48b036 100644
--- a/core-ui/java/android/support/v4/widget/DrawerLayout.java
+++ b/core-ui/java/android/support/v4/widget/DrawerLayout.java
@@ -453,7 +453,7 @@
/**
* Set a simple drawable used for the left or right shadow. The drawable provided must have a
* nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
- * instead of the drawable provided.
+ * instead of using the provided shadow drawable.
*
* <p>Note that for better support for both left-to-right and right-to-left layout
* directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
@@ -492,7 +492,7 @@
/**
* Set a simple drawable used for the left or right shadow. The drawable provided must have a
* nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
- * instead of the drawable provided.
+ * instead of using the provided shadow drawable.
*
* <p>Note that for better support for both left-to-right and right-to-left layout
* directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
diff --git a/design/res/layout/design_bottom_navigation_item.xml b/design/res/layout/design_bottom_navigation_item.xml
index 67df838..f6212cf 100644
--- a/design/res/layout/design_bottom_navigation_item.xml
+++ b/design/res/layout/design_bottom_navigation_item.xml
@@ -26,8 +26,9 @@
<android.support.design.internal.BaselineLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/design_bottom_navigation_margin"
android:layout_gravity="bottom|center_horizontal"
+ android:clipToPadding="false"
+ android:paddingBottom="10dp"
android:duplicateParentState="true">
<TextView
android:id="@+id/smallLabel"
diff --git a/design/src/android/support/design/internal/BaselineLayout.java b/design/src/android/support/design/internal/BaselineLayout.java
index eac3542..23a04cd 100644
--- a/design/src/android/support/design/internal/BaselineLayout.java
+++ b/design/src/android/support/design/internal/BaselineLayout.java
@@ -24,7 +24,8 @@
import android.view.ViewGroup;
/**
- * A simple ViewGroup that aligns all the views inside on a baseline.
+ * A simple ViewGroup that aligns all the views inside on a baseline. Note: bottom padding for this
+ * view will be measured starting from the baseline.
*
* @hide
*/
@@ -69,6 +70,7 @@
ViewCompat.getMeasuredState(child));
}
if (maxChildBaseline != -1) {
+ maxChildDescent = Math.max(maxChildDescent, getPaddingBottom());
maxHeight = Math.max(maxHeight, maxChildBaseline + maxChildDescent);
mBaseline = maxChildBaseline;
}
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
index bc73970..82d983e 100644
--- a/design/src/android/support/design/internal/BottomNavigationMenuView.java
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -255,6 +255,7 @@
}
removeAllViews();
if (mMenu.size() == 0) {
+ mButtons = null;
return;
}
mButtons = new BottomNavigationItemView[mMenu.size()];
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 8fcf796..5f8de36 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -110,7 +110,6 @@
private Toolbar mToolbar;
private View mToolbarDirectChild;
private View mDummyView;
- private int mToolbarDrawIndex;
private int mExpandedMarginStart;
private int mExpandedMarginTop;
@@ -314,16 +313,14 @@
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// This is a little weird. Our scrim needs to be behind the Toolbar (if it is present),
// but in front of any other children which are behind it. To do this we intercept the
- // drawChild() call, and draw our scrim after the preceding view is drawn
- boolean invalidate = super.drawChild(canvas, child, drawingTime);
-
- if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChildDrawnNext(child)) {
+ // drawChild() call, and draw our scrim just before the Toolbar is drawn
+ boolean invalidated = false;
+ if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChild(child)) {
mContentScrim.mutate().setAlpha(mScrimAlpha);
mContentScrim.draw(canvas);
- invalidate = true;
+ invalidated = true;
}
-
- return invalidate;
+ return super.drawChild(canvas, child, drawingTime) || invalidated;
}
@Override
@@ -369,8 +366,10 @@
mRefreshToolbar = false;
}
- private boolean isToolbarChildDrawnNext(View child) {
- return mToolbarDrawIndex >= 0 && mToolbarDrawIndex == indexOfChild(child) + 1;
+ private boolean isToolbarChild(View child) {
+ return (mToolbarDirectChild == null || mToolbarDirectChild == this)
+ ? child == mToolbar
+ : child == mToolbarDirectChild;
}
/**
@@ -480,13 +479,9 @@
}
if (mToolbarDirectChild == null || mToolbarDirectChild == this) {
setMinimumHeight(getHeightWithMargins(mToolbar));
- mToolbarDrawIndex = indexOfChild(mToolbar);
} else {
setMinimumHeight(getHeightWithMargins(mToolbarDirectChild));
- mToolbarDrawIndex = indexOfChild(mToolbarDirectChild);
}
- } else {
- mToolbarDrawIndex = -1;
}
updateScrimVisibility();
@@ -631,6 +626,10 @@
}
}
+ int getScrimAlpha() {
+ return mScrimAlpha;
+ }
+
/**
* Set the drawable to use for the content scrim from resources. Providing null will disable
* the scrim functionality.
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 262fd9f..bac67f9 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -47,6 +47,7 @@
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.util.Pools;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MotionEventCompat;
@@ -133,7 +134,6 @@
static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
new ThreadLocal<>();
-
static final int EVENT_PRE_DRAW = 0;
static final int EVENT_NESTED_SCROLL = 1;
static final int EVENT_VIEW_REMOVED = 2;
@@ -145,17 +145,27 @@
public @interface DispatchChangeEvent {}
static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
+ private static final Pools.Pool<Rect> sRectPool = new Pools.SynchronizedPool<>(12);
+
+ @NonNull
+ private static Rect acquireTempRect() {
+ Rect rect = sRectPool.acquire();
+ if (rect == null) {
+ rect = new Rect();
+ }
+ return rect;
+ }
+
+ private static void releaseTempRect(@NonNull Rect rect) {
+ rect.setEmpty();
+ sRectPool.release(rect);
+ }
private final List<View> mDependencySortedChildren = new ArrayList<>();
private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
private final List<View> mTempList1 = new ArrayList<>();
private final List<View> mTempDependenciesList = new ArrayList<>();
- private final Rect mTempRect1 = new Rect();
- private final Rect mTempRect2 = new Rect();
- private final Rect mTempRect3 = new Rect();
- private final Rect mTempRect4 = new Rect();
- private final Rect mTempRect5 = new Rect();
private final int[] mTempIntPair = new int[2];
private Paint mScrimPaint;
@@ -1047,12 +1057,16 @@
private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final Rect anchorRect = mTempRect1;
- final Rect childRect = mTempRect2;
- getDescendantRect(anchor, anchorRect);
- getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
-
- child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+ final Rect anchorRect = acquireTempRect();
+ final Rect childRect = acquireTempRect();
+ try {
+ getDescendantRect(anchor, anchorRect);
+ getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
+ child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+ } finally {
+ releaseTempRect(anchorRect);
+ releaseTempRect(childRect);
+ }
}
/**
@@ -1131,7 +1145,7 @@
*/
private void layoutChild(View child, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final Rect parent = mTempRect1;
+ final Rect parent = acquireTempRect();
parent.set(getPaddingLeft() + lp.leftMargin,
getPaddingTop() + lp.topMargin,
getWidth() - getPaddingRight() - lp.rightMargin,
@@ -1147,10 +1161,13 @@
parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
}
- final Rect out = mTempRect2;
+ final Rect out = acquireTempRect();
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
child.getMeasuredHeight(), parent, out, layoutDirection);
child.layout(out.left, out.top, out.right, out.bottom);
+
+ releaseTempRect(parent);
+ releaseTempRect(out);
}
/**
@@ -1225,8 +1242,10 @@
final void onChildViewsChanged(@DispatchChangeEvent final int type) {
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int childCount = mDependencySortedChildren.size();
- final Rect inset = mTempRect4;
- inset.setEmpty();
+ final Rect inset = acquireTempRect();
+ final Rect drawRect = acquireTempRect();
+ final Rect lastDrawRect = acquireTempRect();
+
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -1245,7 +1264,6 @@
}
// Get the current draw rect of the view
- final Rect drawRect = mTempRect1;
getChildRect(child, true, drawRect);
// Accumulate inset sizes
@@ -1277,7 +1295,6 @@
if (type == EVENT_PRE_DRAW) {
// Did it change? if not continue
- final Rect lastDrawRect = mTempRect2;
getLastChildRect(child, lastDrawRect);
if (lastDrawRect.equals(drawRect)) {
continue;
@@ -1321,6 +1338,10 @@
}
}
}
+
+ releaseTempRect(inset);
+ releaseTempRect(drawRect);
+ releaseTempRect(lastDrawRect);
}
private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) {
@@ -1329,31 +1350,34 @@
return;
}
- final Rect bounds = mTempRect5;
- bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
- if (bounds.isEmpty()) {
+ if (child.getWidth() <= 0 || child.getHeight() <= 0) {
// Bounds are empty so there is nothing to dodge against, skip...
return;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior behavior = lp.getBehavior();
- final Rect dodgeRect = mTempRect3;
- dodgeRect.setEmpty();
+ final Rect dodgeRect = acquireTempRect();
+ final Rect bounds = acquireTempRect();
+ bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) {
// Make sure that the rect is within the view's bounds
if (!bounds.contains(dodgeRect)) {
- throw new IllegalArgumentException("Rect should be within the child's bounds."
- + " Rect:" + dodgeRect.toShortString()
- + " | Bounds:" + bounds.toShortString());
+ throw new IllegalArgumentException("Rect should be within the child's bounds."
+ + " Rect:" + dodgeRect.toShortString()
+ + " | Bounds:" + bounds.toShortString());
}
} else {
dodgeRect.set(bounds);
}
+ // We can release the bounds rect now
+ releaseTempRect(bounds);
+
if (dodgeRect.isEmpty()) {
// Rect is empty so there is nothing to dodge against, skip...
+ releaseTempRect(dodgeRect);
return;
}
@@ -1397,6 +1421,8 @@
if (!offsetX) {
setInsetOffsetX(child, 0);
}
+
+ releaseTempRect(dodgeRect);
}
private void setInsetOffsetX(View child, int offsetX) {
@@ -1558,9 +1584,9 @@
void offsetChildToAnchor(View child, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.mAnchorView != null) {
- final Rect anchorRect = mTempRect1;
- final Rect childRect = mTempRect2;
- final Rect desiredChildRect = mTempRect3;
+ final Rect anchorRect = acquireTempRect();
+ final Rect childRect = acquireTempRect();
+ final Rect desiredChildRect = acquireTempRect();
getDescendantRect(lp.mAnchorView, anchorRect);
getChildRect(child, false, childRect);
@@ -1590,6 +1616,10 @@
b.onDependentViewChanged(this, child, lp.mAnchorView);
}
}
+
+ releaseTempRect(anchorRect);
+ releaseTempRect(childRect);
+ releaseTempRect(desiredChildRect);
}
}
@@ -1603,9 +1633,13 @@
* @return true if the point is within the child view's bounds, false otherwise
*/
public boolean isPointInChildBounds(View child, int x, int y) {
- final Rect r = mTempRect1;
+ final Rect r = acquireTempRect();
getDescendantRect(child, r);
- return r.contains(x, y);
+ try {
+ return r.contains(x, y);
+ } finally {
+ releaseTempRect(r);
+ }
}
/**
@@ -1618,13 +1652,17 @@
*/
public boolean doViewsOverlap(View first, View second) {
if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
- final Rect firstRect = mTempRect1;
+ final Rect firstRect = acquireTempRect();
getChildRect(first, first.getParent() != this, firstRect);
- final Rect secondRect = mTempRect2;
+ final Rect secondRect = acquireTempRect();
getChildRect(second, second.getParent() != this, secondRect);
-
- return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
- || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
+ try {
+ return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
+ || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
+ } finally {
+ releaseTempRect(firstRect);
+ releaseTempRect(secondRect);
+ }
}
return false;
}
@@ -1954,6 +1992,10 @@
* <p>Once a Behavior intercepts touch events, the rest of the event stream will
* be sent to the {@link #onTouchEvent} method.</p>
*
+ * <p>This method will be called regardless of the visibility of the associated child
+ * of the behavior. If you only wish to handle touch events when the child is visible, you
+ * should add a check to {@link View#isShown()} on the given child.</p>
+ *
* <p>The default implementation of this method always returns false.</p>
*
* @param parent the parent view currently receiving this touch event
@@ -1975,6 +2017,10 @@
* UI pane open or closed. This method should perform actual mutations of view
* layout state.</p>
*
+ * <p>This method will be called regardless of the visibility of the associated child
+ * of the behavior. If you only wish to handle touch events when the child is visible, you
+ * should add a check to {@link View#isShown()} on the given child.</p>
+ *
* @param parent the parent view currently receiving this touch event
* @param child the child view associated with this Behavior
* @param ev the MotionEvent describing the touch event being processed
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 92c1506..22f4546 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -1076,6 +1076,13 @@
});
}
+ if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
+ // We should make sure that the EditText has the same min-height as the password
+ // toggle view. This ensure focus works properly, and there is no visual jump
+ // if the password toggle is enabled/disabled.
+ mEditText.setMinimumHeight(ViewCompat.getMinimumHeight(mPasswordToggleView));
+ }
+
mPasswordToggleView.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
diff --git a/design/tests/res/layout/design_navigation_view.xml b/design/tests/res/layout/design_navigation_view.xml
index 1789843..4f7147d 100644
--- a/design/tests/res/layout/design_navigation_view.xml
+++ b/design/tests/res/layout/design_navigation_view.xml
@@ -37,7 +37,7 @@
android:fitsSystemWindows="true" tells the system to have
DrawerLayout span the full height of the screen, including the
system status bar on Lollipop+ versions of the plaform. -->
- <android.support.design.widget.NavigationView
+ <android.support.design.widget.NavigationTestView
android:id="@+id/start_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/design/tests/src/android/support/design/testutils/CollapsingToolbarLayoutActions.java b/design/tests/src/android/support/design/testutils/CollapsingToolbarLayoutActions.java
new file mode 100644
index 0000000..71dd150
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/CollapsingToolbarLayoutActions.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import android.support.annotation.ColorInt;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+public class CollapsingToolbarLayoutActions {
+
+ public static ViewAction setContentScrimColor(@ColorInt final int color) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set the content scrim to a color";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+ CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) view;
+ ctl.setContentScrimColor(color);
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
index 39486e5..fc131a4 100644
--- a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
@@ -16,16 +16,21 @@
package android.support.design.widget;
+import static android.support.design.testutils.CollapsingToolbarLayoutActions.setContentScrimColor;
import static android.support.design.testutils.TestUtilsActions.setText;
import static android.support.design.testutils.TestUtilsActions.setTitle;
import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.junit.Assert.assertEquals;
+import android.graphics.Color;
import android.os.Build;
+import android.os.SystemClock;
import android.support.annotation.CallSuper;
import android.support.annotation.IdRes;
+import android.support.annotation.IntRange;
import android.support.annotation.LayoutRes;
import android.support.annotation.StringRes;
import android.support.design.test.R;
@@ -41,6 +46,10 @@
import android.view.View;
import android.widget.TextView;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
public abstract class AppBarLayoutBaseTest extends BaseDynamicCoordinatorLayoutTest {
protected AppBarLayout mAppBar;
@@ -110,13 +119,15 @@
final CharSequence activityTitle = activity.getString(titleResId);
activity.setTitle(activityTitle);
if (mCollapsingToolbar != null) {
- onView(withId(R.id.collapsing_app_bar)).perform(setTitle(activityTitle));
+ onView(withId(R.id.collapsing_app_bar))
+ .perform(setTitle(activityTitle))
+ .perform(setContentScrimColor(Color.MAGENTA));
}
TextView dialog = (TextView) mCoordinatorLayout.findViewById(R.id.textview_dialogue);
if (dialog != null) {
- onView(withId(R.id.textview_dialogue)).perform(
- setText(TextUtils.concat(Shakespeare.DIALOGUE)));
+ onView(withId(R.id.textview_dialogue))
+ .perform(setText(TextUtils.concat(Shakespeare.DIALOGUE)));
}
mDefaultElevationValue = mAppBar.getResources()
@@ -128,4 +139,25 @@
assertEquals(expectedValue, ViewCompat.getElevation(mAppBar), 0.05f);
}
}
+
+ protected void assertScrimAlpha(@IntRange(from = 0, to = 255) int alpha) {
+ SystemClock.sleep(300);
+ onView(withId(R.id.collapsing_app_bar))
+ .check(matches(withScrimAlpha(alpha)));
+ }
+
+ static Matcher withScrimAlpha(final int alpha) {
+ return new TypeSafeMatcher<CollapsingToolbarLayout>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(
+ "CollapsingToolbarLayout has content scrim with alpha: " + alpha);
+ }
+
+ @Override
+ protected boolean matchesSafely(CollapsingToolbarLayout view) {
+ return alpha == view.getScrimAlpha();
+ }
+ };
+ }
}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
index 84e1abe..adcfee8 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -55,6 +55,7 @@
final int shortSwipeAmount = toolbarHeight;
assertAppBarElevation(0f);
+ assertScrimAlpha(0);
// Perform a swipe-up gesture across the horizontal center of the screen.
performVerticalSwipeUpGesture(
@@ -82,6 +83,7 @@
assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
appbarOnScreenXY[1] + appbarHeight, 1);
assertAppBarElevation(mDefaultElevationValue);
+ assertScrimAlpha(255);
// Perform a short swipe-down gesture across the horizontal center of the screen.
// Note that the swipe down is a bit longer than the swipe up to check that the app bar
@@ -99,6 +101,7 @@
assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
appbarOnScreenXY[1] + appbarHeight, 1);
assertAppBarElevation(mDefaultElevationValue);
+ assertScrimAlpha(255);
// Perform another swipe-down gesture across the horizontal center of the screen.
performVerticalSwipeDownGesture(
@@ -113,6 +116,7 @@
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
assertAppBarElevation(0f);
+ assertScrimAlpha(0);
// Perform yet another swipe-down gesture across the horizontal center of the screen.
performVerticalSwipeDownGesture(
@@ -127,6 +131,7 @@
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
assertAppBarElevation(0f);
+ assertScrimAlpha(0);
}
@Test
@@ -157,6 +162,7 @@
final int shortSwipeAmount = toolbarHeight;
assertAppBarElevation(0f);
+ assertScrimAlpha(0);
// Perform a swipe-up gesture across the horizontal center of the screen, starting from
// just below the AppBarLayout
@@ -173,6 +179,7 @@
// Allow for off-by-a-pixel margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
assertAppBarElevation(0f);
+ assertScrimAlpha(255);
// Perform another swipe-up gesture
performVerticalSwipeUpGesture(
@@ -186,6 +193,7 @@
// margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
assertAppBarElevation(0f);
+ assertScrimAlpha(255);
// Perform a short swipe-down gesture across the horizontal center of the screen.
// Note that the swipe down is a bit longer than the swipe up to fully bring down
@@ -203,6 +211,7 @@
assertEquals(originalAppbarTop + toolbarHeight + topInset,
appbarOnScreenXY[1] + appbarHeight, 1);
assertAppBarElevation(mDefaultElevationValue);
+ assertScrimAlpha(255);
// Perform another swipe-down gesture across the horizontal center of the screen.
performVerticalSwipeDownGesture(
@@ -217,6 +226,7 @@
assertEquals(originalAppbarTop, appbarOnScreenXY[1]);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight);
assertAppBarElevation(0f);
+ assertScrimAlpha(0);
// Perform yet another swipe-down gesture across the horizontal center of the screen.
performVerticalSwipeDownGesture(
@@ -231,6 +241,7 @@
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
assertAppBarElevation(0f);
+ assertScrimAlpha(0);
}
@Test
diff --git a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
index 37a58a6..f06a85a 100644
--- a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
@@ -223,6 +223,16 @@
checkAndVerifyExclusiveItem(menu, R.id.destination_people);
}
+ @UiThreadTest
+ @Test
+ @SmallTest
+ public void testClearingMenu() throws Throwable {
+ mBottomNavigation.getMenu().clear();
+ assertEquals(0, mBottomNavigation.getMenu().size());
+ mBottomNavigation.inflateMenu(R.menu.bottom_navigation_view_content);
+ assertEquals(3, mBottomNavigation.getMenu().size());
+ }
+
private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable {
menu.findItem(id).setChecked(true);
for (int i = 0; i < menu.size(); i++) {
diff --git a/design/tests/src/android/support/design/widget/NavigationTestView.java b/design/tests/src/android/support/design/widget/NavigationTestView.java
new file mode 100644
index 0000000..af3d69e
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/NavigationTestView.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import android.content.Context;
+import android.support.v4.view.WindowInsetsCompat;
+import android.util.AttributeSet;
+
+/**
+ * Expose hasSystemWindowInsets() for testing.
+ */
+public class NavigationTestView extends NavigationView {
+
+ boolean mHashSystemWindowInsets;
+
+ public NavigationTestView(Context context) {
+ this(context, null);
+ }
+
+ public NavigationTestView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NavigationTestView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onInsetsChanged(WindowInsetsCompat insets) {
+ super.onInsetsChanged(insets);
+ mHashSystemWindowInsets = insets.hasSystemWindowInsets();
+ }
+
+ public boolean hasSystemWindowInsets() {
+ return mHashSystemWindowInsets;
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
index 7031281..f0c064e 100755
--- a/design/tests/src/android/support/design/widget/NavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -94,7 +94,7 @@
private DrawerLayout mDrawerLayout;
- private NavigationView mNavigationView;
+ private NavigationTestView mNavigationView;
public NavigationViewTest() {
super(NavigationViewActivity.class);
@@ -104,7 +104,7 @@
public void setUp() throws Exception {
final NavigationViewActivity activity = mActivityTestRule.getActivity();
mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout);
- mNavigationView = (NavigationView) mDrawerLayout.findViewById(R.id.start_drawer);
+ mNavigationView = (NavigationTestView) mDrawerLayout.findViewById(R.id.start_drawer);
// Close the drawer to reset the state for the next test
onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
@@ -146,7 +146,11 @@
onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
if (Build.VERSION.SDK_INT >= 21) {
- assertFalse(mNavigationView.willNotDraw());
+ if (mNavigationView.hasSystemWindowInsets()) {
+ assertFalse(mNavigationView.willNotDraw());
+ } else {
+ assertTrue(mNavigationView.willNotDraw());
+ }
} else {
assertTrue(mNavigationView.willNotDraw());
}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 30c15b5..4944170 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -31,6 +31,7 @@
import static android.support.design.testutils.TextInputLayoutActions.setTypeface;
import static android.support.design.testutils.TextInputLayoutMatchers
.hasPasswordToggleContentDescription;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
@@ -38,6 +39,7 @@
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.AccessibilityChecks.accessibilityAssertion;
import static android.support.test.espresso.matcher.ViewMatchers.hasContentDescription;
+import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
@@ -66,6 +68,7 @@
import android.support.v4.widget.TextViewCompat;
import android.util.AttributeSet;
import android.util.SparseArray;
+import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
@@ -456,6 +459,21 @@
.check(matches(withTextColor(textColor)));
}
+ @Test
+ public void testFocusMovesToEditTextWithPasswordEnabled() {
+ // Focus the preceding EditText
+ onView(withId(R.id.textinput_edittext))
+ .perform(click())
+ .check(matches(hasFocus()));
+
+ // Then send a TAB to focus the next view
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+
+ // And check that the EditText is focused
+ onView(withId(R.id.textinput_edittext_pwd))
+ .check(matches(hasFocus()));
+ }
+
static ViewAssertion isHintExpanded(final boolean expanded) {
return new ViewAssertion() {
@Override
diff --git a/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java b/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
index a8323dc..879be4a 100644
--- a/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -24,7 +24,6 @@
import android.transition.TransitionSet;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import java.util.ArrayList;
import java.util.List;
@@ -343,18 +342,15 @@
}
}
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- for (int i = 0; i < numSharedElements; i++) {
- sharedElementsIn.get(i).setTransitionName(inNames.get(i));
- sharedElementsOut.get(i).setTransitionName(outNames.get(i));
- }
- return true;
- }
- });
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 0; i < numSharedElements; i++) {
+ sharedElementsIn.get(i).setTransitionName(inNames.get(i));
+ sharedElementsOut.get(i).setTransitionName(outNames.get(i));
+ }
+ }
+ });
}
/**
@@ -406,23 +402,20 @@
public static void setNameOverridesUnoptimized(final View sceneRoot,
final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- final int numSharedElements = sharedElementsIn.size();
- for (int i = 0; i < numSharedElements; i++) {
- View view = sharedElementsIn.get(i);
- String name = view.getTransitionName();
- if (name != null) {
- String inName = findKeyForValue(nameOverrides, name);
- view.setTransitionName(inName);
- }
- }
- return true;
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ final int numSharedElements = sharedElementsIn.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ View view = sharedElementsIn.get(i);
+ String name = view.getTransitionName();
+ if (name != null) {
+ String inName = findKeyForValue(nameOverrides, name);
+ view.setTransitionName(inName);
}
- });
+ }
+ }
+ });
}
/**
@@ -566,20 +559,17 @@
public static void scheduleNameReset(final ViewGroup sceneRoot,
final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- final int numSharedElements = sharedElementsIn.size();
- for (int i = 0; i < numSharedElements; i++) {
- final View view = sharedElementsIn.get(i);
- final String name = view.getTransitionName();
- final String inName = nameOverrides.get(name);
- view.setTransitionName(inName);
- }
- return true;
- }
- });
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ final int numSharedElements = sharedElementsIn.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ final View view = sharedElementsIn.get(i);
+ final String name = view.getTransitionName();
+ final String inName = nameOverrides.get(name);
+ view.setTransitionName(inName);
+ }
+ }
+ });
}
}
diff --git a/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java b/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java
new file mode 100644
index 0000000..502af1f
--- /dev/null
+++ b/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * An OnPreDrawListener that will remove itself after one OnPreDraw call. Typical
+ * usage is:
+ * <pre><code>
+ * OneShotPreDrawListener.add(view, () -> { view.doSomething(); })
+ * </code></pre>
+ * <p>
+ * The onPreDraw always returns true.
+ * <p>
+ * The listener will also remove itself from the ViewTreeObserver when the view
+ * is detached from the view hierarchy. In that case, the Runnable will never be
+ * executed.
+ */
+class OneShotPreDrawListener implements ViewTreeObserver.OnPreDrawListener,
+ View.OnAttachStateChangeListener {
+ private final View mView;
+ private ViewTreeObserver mViewTreeObserver;
+ private final Runnable mRunnable;
+
+ private OneShotPreDrawListener(View view, Runnable runnable) {
+ mView = view;
+ mViewTreeObserver = view.getViewTreeObserver();
+ mRunnable = runnable;
+ }
+
+ /**
+ * Creates a OneShotPreDrawListener and adds it to view's ViewTreeObserver.
+ * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen.
+ * @param runnable The Runnable to execute in the OnPreDraw (once)
+ * @return The added OneShotPreDrawListener. It can be removed prior to
+ * the onPreDraw by calling {@link #removeListener()}.
+ */
+ public static OneShotPreDrawListener add(View view, Runnable runnable) {
+ OneShotPreDrawListener listener = new OneShotPreDrawListener(view, runnable);
+ view.getViewTreeObserver().addOnPreDrawListener(listener);
+ view.addOnAttachStateChangeListener(listener);
+ return listener;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ removeListener();
+ mRunnable.run();
+ return true;
+ }
+
+ /**
+ * Removes the listener from the ViewTreeObserver. This is useful to call if the
+ * callback should be removed prior to {@link #onPreDraw()}.
+ */
+ public void removeListener() {
+ if (mViewTreeObserver.isAlive()) {
+ mViewTreeObserver.removeOnPreDrawListener(this);
+ } else {
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ }
+ mView.removeOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mViewTreeObserver = v.getViewTreeObserver();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ removeListener();
+ }
+}
diff --git a/fragment/java/android/support/v4/app/BackStackRecord.java b/fragment/java/android/support/v4/app/BackStackRecord.java
index 85ded71..acfa924 100644
--- a/fragment/java/android/support/v4/app/BackStackRecord.java
+++ b/fragment/java/android/support/v4/app/BackStackRecord.java
@@ -216,7 +216,7 @@
ArrayList<String> mSharedElementSourceNames;
ArrayList<String> mSharedElementTargetNames;
- boolean mAllowOptimization = true;
+ boolean mAllowOptimization = false;
@Override
public String toString() {
@@ -513,6 +513,12 @@
if (mSharedElementSourceNames == null) {
mSharedElementSourceNames = new ArrayList<String>();
mSharedElementTargetNames = new ArrayList<String>();
+ } else if (mSharedElementTargetNames.contains(name)) {
+ throw new IllegalArgumentException("A shared element with the target name '"
+ + name + "' has already been added to the transaction.");
+ } else if (mSharedElementSourceNames.contains(transitionName)) {
+ throw new IllegalArgumentException("A shared element with the source name '"
+ + transitionName + " has already been added to the transaction.");
}
mSharedElementSourceNames.add(transitionName);
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index 829e974..ecc24d0 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -1306,15 +1306,17 @@
}
if (container != null) {
container.addView(f.mView);
- f.mIsNewlyAdded = true;
}
if (f.mHidden) {
f.mView.setVisibility(View.GONE);
- f.mIsNewlyAdded = false; // No animation
}
f.onViewCreated(f.mView, f.mSavedFragmentState);
dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
false);
+ // Only animate the view if it is visible. This is done after
+ // dispatchOnFragmentViewCreated in case visibility is changed
+ f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
+ && f.mContainer != null;
} else {
f.mInnerView = null;
}
@@ -2353,7 +2355,10 @@
final int stateAfterAnimating = fragment.getStateAfterAnimating();
final View animatingAway = fragment.getAnimatingAway();
fragment.setAnimatingAway(null);
- animatingAway.clearAnimation();
+ Animation animation = animatingAway.getAnimation();
+ if (animation != null) {
+ animation.cancel();
+ }
moveToState(fragment, stateAfterAnimating, 0, 0, false);
}
}
diff --git a/fragment/java/android/support/v4/app/FragmentTransaction.java b/fragment/java/android/support/v4/app/FragmentTransaction.java
index b03f002..0171681 100644
--- a/fragment/java/android/support/v4/app/FragmentTransaction.java
+++ b/fragment/java/android/support/v4/app/FragmentTransaction.java
@@ -296,7 +296,7 @@
* With optimization, fragment B cannot expect fragment A to exist when
* it has been created because fragment A's add/remove will be optimized out.
* <p>
- * The default is {@code true}.
+ * The default is {@code false}.
*
* @param allowOptimization {@code true} to enable optimizing operations
* or {@code false} to disable optimizing
diff --git a/fragment/java/android/support/v4/app/FragmentTransition.java b/fragment/java/android/support/v4/app/FragmentTransition.java
index 9efd1bc..aa64859 100644
--- a/fragment/java/android/support/v4/app/FragmentTransition.java
+++ b/fragment/java/android/support/v4/app/FragmentTransition.java
@@ -22,7 +22,6 @@
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import java.util.ArrayList;
import java.util.Collection;
@@ -250,15 +249,12 @@
FragmentTransitionCompat21.scheduleHideFragmentView(exitTransition,
exitingFragment.getView(), exitingViews);
final ViewGroup container = exitingFragment.mContainer;
- container.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- container.getViewTreeObserver().removeOnPreDrawListener(this);
- setViewVisibility(exitingViews, View.INVISIBLE);
- return true;
- }
- });
+ OneShotPreDrawListener.add(container, new Runnable() {
+ @Override
+ public void run() {
+ setViewVisibility(exitingViews, View.INVISIBLE);
+ }
+ });
}
}
@@ -356,33 +352,29 @@
final ArrayList<View> sharedElementsIn,
final Object enterTransition, final ArrayList<View> enteringViews,
final Object exitTransition, final ArrayList<View> exitingViews) {
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ if (enterTransition != null) {
+ FragmentTransitionCompat21.removeTarget(enterTransition,
+ nonExistentView);
+ ArrayList<View> views = configureEnteringExitingViews(
+ enterTransition, inFragment, sharedElementsIn, nonExistentView);
+ enteringViews.addAll(views);
+ }
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-
- if (enterTransition != null) {
- FragmentTransitionCompat21.removeTarget(enterTransition,
- nonExistentView);
- ArrayList<View> views = configureEnteringExitingViews(
- enterTransition, inFragment, sharedElementsIn, nonExistentView);
- enteringViews.addAll(views);
- }
-
- if (exitingViews != null) {
- ArrayList<View> tempExiting = new ArrayList<>();
- tempExiting.add(nonExistentView);
- FragmentTransitionCompat21.replaceTargets(exitTransition, exitingViews,
- tempExiting);
- exitingViews.clear();
- exitingViews.add(nonExistentView);
- }
-
- return true;
+ if (exitingViews != null) {
+ if (exitTransition != null) {
+ ArrayList<View> tempExiting = new ArrayList<>();
+ tempExiting.add(nonExistentView);
+ FragmentTransitionCompat21.replaceTargets(exitTransition, exitingViews,
+ tempExiting);
}
- });
+ exitingViews.clear();
+ exitingViews.add(nonExistentView);
+ }
+ }
+ });
}
/**
@@ -486,9 +478,17 @@
if (nameOverrides.isEmpty()) {
sharedElementTransition = null;
+ if (outSharedElements != null) {
+ outSharedElements.clear();
+ }
+ if (inSharedElements != null) {
+ inSharedElements.clear();
+ }
} else {
- sharedElementsOut.addAll(outSharedElements.values());
- sharedElementsIn.addAll(inSharedElements.values());
+ addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements,
+ nameOverrides.keySet());
+ addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements,
+ nameOverrides.values());
}
if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
@@ -519,23 +519,39 @@
epicenterView = null;
}
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- callSharedElementStartEnd(inFragment, outFragment, inIsPop,
- inSharedElements, false);
- if (epicenterView != null) {
- FragmentTransitionCompat21.getBoundsOnScreen(epicenterView, epicenter);
- }
- return true;
- }
- });
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+ inSharedElements, false);
+ if (epicenterView != null) {
+ FragmentTransitionCompat21.getBoundsOnScreen(epicenterView, epicenter);
+ }
+ }
+ });
return sharedElementTransition;
}
/**
+ * Add Views from sharedElements into views that have the transitionName in the
+ * nameOverridesSet.
+ *
+ * @param views Views list to add shared elements to
+ * @param sharedElements List of shared elements
+ * @param nameOverridesSet The transition names for all views to be copied from
+ * sharedElements to views.
+ */
+ private static void addSharedElementsWithMatchingNames(ArrayList<View> views,
+ ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) {
+ for (int i = sharedElements.size() - 1; i >= 0; i--) {
+ View view = sharedElements.valueAt(i);
+ if (nameOverridesSet.contains(ViewCompat.getTransitionName(view))) {
+ views.add(view);
+ }
+ }
+ }
+
+ /**
* Configures the shared elements of an unoptimized fragment transaction's transition.
* This retrieves the shared elements of the incoming fragments, and schedules capturing
* the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
@@ -612,38 +628,36 @@
inEpicenter = null;
}
+
final Object finalSharedElementTransition = sharedElementTransition;
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ ArrayMap<String, View> inSharedElements = captureInSharedElements(
+ nameOverrides, finalSharedElementTransition, fragments);
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- ArrayMap<String, View> inSharedElements = captureInSharedElements(
- nameOverrides, finalSharedElementTransition, fragments);
+ if (inSharedElements != null) {
+ sharedElementsIn.addAll(inSharedElements.values());
+ sharedElementsIn.add(nonExistentView);
+ }
- if (inSharedElements != null) {
- sharedElementsIn.addAll(inSharedElements.values());
- sharedElementsIn.add(nonExistentView);
- }
+ callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+ inSharedElements, false);
+ if (finalSharedElementTransition != null) {
+ FragmentTransitionCompat21.swapSharedElementTargets(
+ finalSharedElementTransition, sharedElementsOut,
+ sharedElementsIn);
- callSharedElementStartEnd(inFragment, outFragment, inIsPop,
- inSharedElements, false);
- if (finalSharedElementTransition != null) {
- FragmentTransitionCompat21.swapSharedElementTargets(
- finalSharedElementTransition, sharedElementsOut,
- sharedElementsIn);
-
- final View inEpicenterView = getInEpicenterView(inSharedElements,
- fragments, enterTransition, inIsPop);
- if (inEpicenterView != null) {
- FragmentTransitionCompat21.getBoundsOnScreen(inEpicenterView,
- inEpicenter);
- }
- }
- return true;
+ final View inEpicenterView = getInEpicenterView(inSharedElements,
+ fragments, enterTransition, inIsPop);
+ if (inEpicenterView != null) {
+ FragmentTransitionCompat21.getBoundsOnScreen(inEpicenterView,
+ inEpicenter);
}
- });
+ }
+ }
+ });
+
return sharedElementTransition;
}
diff --git a/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java b/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java
index 9f6b263..873e36d 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java
@@ -234,6 +234,7 @@
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.add(R.id.fragmentContainer, fragment)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -258,6 +259,7 @@
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.remove(fragment)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -274,6 +276,7 @@
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -284,6 +287,7 @@
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -307,6 +311,7 @@
final AnimatorFragment fragment1 = new AnimatorFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
assertEquals(0, fragment1.numAnimators);
@@ -318,6 +323,7 @@
.setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
diff --git a/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java b/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java
index 39dbf11..08be463 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java
@@ -62,10 +62,12 @@
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.executePendingTransactions();
}
@@ -90,7 +92,11 @@
public void startWithPop() throws Throwable {
// Start with a single fragment on the back stack
final CountCallsFragment fragment1 = new CountCallsFragment();
- mFM.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
FragmentTestUtil.assertChildren(mContainer, fragment1);
@@ -103,6 +109,7 @@
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.executePendingTransactions();
}
@@ -126,11 +133,13 @@
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.popBackStack();
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.executePendingTransactions();
}
@@ -156,9 +165,18 @@
id[0] = mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
- mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().remove(fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .remove(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
mFM.executePendingTransactions();
}
});
@@ -186,6 +204,7 @@
int id = mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
assertEquals(1, fragment1.onCreateViewCount);
@@ -196,10 +215,12 @@
mFM.beginTransaction()
.remove(fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.executePendingTransactions();
}
@@ -223,6 +244,7 @@
int id = mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
assertEquals(1, fragment1.onAttachCount);
@@ -231,9 +253,21 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
mFM.executePendingTransactions();
}
});
@@ -268,6 +302,7 @@
.add(R.id.fragmentContainer, fragment1)
.detach(fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
@@ -281,9 +316,21 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
mFM.executePendingTransactions();
}
});
@@ -316,6 +363,7 @@
.add(R.id.fragmentContainer, fragment1)
.hide(fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
assertEquals(0, fragment1.onShowCount);
@@ -328,18 +376,22 @@
mFM.beginTransaction()
.show(fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.remove(fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.hide(fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.executePendingTransactions();
}
@@ -360,8 +412,16 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- mFM.beginTransaction().show(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
mFM.executePendingTransactions();
}
});
@@ -380,10 +440,26 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- mFM.beginTransaction().show(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
mFM.executePendingTransactions();
}
});
@@ -406,6 +482,7 @@
int id = mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
assertEquals(0, fragment1.onShowCount);
@@ -415,10 +492,26 @@
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
- mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
- mFM.beginTransaction().show(fragment1).addToBackStack(null).commit();
+ mFM.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
+ mFM.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setAllowOptimization(true)
+ .commit();
mFM.executePendingTransactions();
}
});
@@ -443,6 +536,7 @@
int id = mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.executePendingTransactions(mActivityRule);
FragmentTestUtil.assertChildren(mContainer, fragment1);
@@ -455,10 +549,12 @@
mFM.beginTransaction()
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.executePendingTransactions();
@@ -480,6 +576,7 @@
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.popBackStack();
mFM.executePendingTransactions();
@@ -503,9 +600,11 @@
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.replace(R.id.fragmentContainer, fragment2)
+ .setAllowOptimization(true)
.commit();
mFM.popBackStack();
mFM.executePendingTransactions();
@@ -529,6 +628,7 @@
mFM.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
mFM.beginTransaction()
.replace(R.id.fragmentContainer, fragment2)
diff --git a/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java b/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java
index bc8e9e1..ab319b7 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -26,6 +27,7 @@
import android.app.Instrumentation;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Bundle;
import android.support.fragment.test.R;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
@@ -440,6 +442,7 @@
.addSharedElement(startGreen, "greenSquare")
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -592,6 +595,109 @@
verifyNoOtherTransitions(fragment2);
}
+ // Ensure that shared element without matching transition name doesn't error out
+ @Test
+ public void sharedElementMismatch() throws Throwable {
+ final TransitionFragment fragment1 = setupInitialFragment();
+
+ // Now do a transition to scene2
+ TransitionFragment fragment2 = new TransitionFragment();
+ fragment2.setLayoutId(R.layout.scene2);
+
+ final View startBlue = findBlue();
+ final View startGreen = findGreen();
+ final Rect startBlueBounds = getBoundsOnScreen(startBlue);
+
+ mFragmentManager.beginTransaction()
+ .addSharedElement(startBlue, "fooSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .setAllowOptimization(mOptimize)
+ .addToBackStack(null)
+ .commit();
+ FragmentTestUtil.waitForExecution(mActivityRule);
+
+ fragment1.waitForTransition();
+ fragment2.waitForTransition();
+
+ final View endBlue = findBlue();
+ final View endGreen = findGreen();
+
+ if (mOptimize) {
+ verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
+ } else {
+ verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen);
+ verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue);
+ }
+ verifyNoOtherTransitions(fragment1);
+
+ verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
+ verifyNoOtherTransitions(fragment2);
+ }
+
+ // Ensure that using the same source or target shared element results in an exception.
+ @Test
+ public void sharedDuplicateTargetNames() throws Throwable {
+ setupInitialFragment();
+
+ final View startBlue = findBlue();
+ final View startGreen = findGreen();
+
+ FragmentTransaction ft = mFragmentManager.beginTransaction();
+ ft.addSharedElement(startBlue, "blueSquare");
+ try {
+ ft.addSharedElement(startGreen, "blueSquare");
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ ft.addSharedElement(startBlue, "greenSquare");
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ // Test that invisible fragment views don't participate in transitions
+ @Test
+ public void invisibleNoTransitions() throws Throwable {
+ if (!mOptimize) {
+ return; // only optimized transitions can avoid interaction
+ }
+ // enter transition
+ TransitionFragment fragment = new InvisibleFragment();
+ fragment.setLayoutId(R.layout.scene1);
+ mFragmentManager.beginTransaction()
+ .setAllowOptimization(mOptimize)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit();
+ FragmentTestUtil.waitForExecution(mActivityRule);
+ fragment.waitForNoTransition();
+ verifyNoOtherTransitions(fragment);
+
+ // exit transition
+ mFragmentManager.beginTransaction()
+ .setAllowOptimization(mOptimize)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit();
+
+ fragment.waitForNoTransition();
+ verifyNoOtherTransitions(fragment);
+
+ // reenter transition
+ FragmentTestUtil.popBackStackImmediate(mActivityRule);
+ fragment.waitForNoTransition();
+ verifyNoOtherTransitions(fragment);
+
+ // return transition
+ FragmentTestUtil.popBackStackImmediate(mActivityRule);
+ fragment.waitForNoTransition();
+ verifyNoOtherTransitions(fragment);
+ }
+
private TransitionFragment setupInitialFragment() throws Throwable {
TransitionFragment fragment1 = new TransitionFragment();
fragment1.setLayoutId(R.layout.scene1);
@@ -888,6 +994,13 @@
setSharedElementEnterTransition(sharedElementEnterTransition);
setSharedElementReturnTransition(sharedElementReturnTransition);
}
+ }
+ public static class InvisibleFragment extends TransitionFragment {
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ view.setVisibility(View.INVISIBLE);
+ super.onViewCreated(view, savedInstanceState);
+ }
}
}
diff --git a/fragment/tests/java/android/support/v4/app/FragmentViewTests.java b/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
index 22a52ed..ad970c8 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.fail;
import android.app.Instrumentation;
+import android.os.Bundle;
import android.support.fragment.test.R;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
@@ -873,6 +874,34 @@
assertNotNull(findViewById(R.id.textC));
}
+ // Test that adding a fragment with invisible or gone views does not end up with the view
+ // being visible
+ @Test
+ public void addInvisibleAndGoneFragments() throws Throwable {
+ FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+ ViewGroup container = (ViewGroup)
+ mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+
+ final StrictViewFragment fragment1 = new InvisibleFragment();
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
+ FragmentTestUtil.executePendingTransactions(mActivityRule);
+ FragmentTestUtil.assertChildren(container, fragment1);
+
+ assertEquals(View.INVISIBLE, fragment1.getView().getVisibility());
+
+ final InvisibleFragment fragment2 = new InvisibleFragment();
+ fragment2.visibility = View.GONE;
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit();
+ FragmentTestUtil.executePendingTransactions(mActivityRule);
+ FragmentTestUtil.assertChildren(container, fragment2);
+
+ assertEquals(View.GONE, fragment2.getView().getVisibility());
+ }
+
private View findViewById(int viewId) {
return mActivityRule.getActivity().findViewById(viewId);
}
@@ -886,4 +915,14 @@
fragments[i].getView());
}
}
+
+ public static class InvisibleFragment extends StrictViewFragment {
+ public int visibility = View.INVISIBLE;
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ view.setVisibility(visibility);
+ super.onViewCreated(view, savedInstanceState);
+ }
+ }
}
diff --git a/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java b/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java
index b20298d..fe41976 100644
--- a/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java
+++ b/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java
@@ -61,6 +61,7 @@
mBeginningFragment = new PostponedFragment1();
fm.beginTransaction()
.add(R.id.fragmentContainer, mBeginningFragment)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -81,6 +82,7 @@
.addSharedElement(startBlue, "blueSquare")
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -126,12 +128,14 @@
.addSharedElement(startBlue, "blueSquare")
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
fm.beginTransaction()
.addSharedElement(startBlue, "blueSquare")
.replace(R.id.fragmentContainer, fragment3)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
}
});
@@ -176,6 +180,7 @@
.addSharedElement(startBlue, "blueSquare")
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -187,6 +192,7 @@
.addSharedElement(startBlue, "blueSquare")
.replace(R.id.fragmentContainer, fragment3)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
// This should cancel the mBeginningFragment -> fragment2 transition
@@ -238,6 +244,7 @@
.attach(fragment2)
.show(fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -266,7 +273,10 @@
@Test
public void differentContainers() throws Throwable {
final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction().remove(mBeginningFragment).commit();
+ fm.beginTransaction()
+ .remove(mBeginningFragment)
+ .setAllowOptimization(true)
+ .commit();
FragmentTestUtil.waitForExecution(mActivityRule);
FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
@@ -276,6 +286,7 @@
fm.beginTransaction()
.add(R.id.fragmentContainer1, fragment1)
.add(R.id.fragmentContainer2, fragment2)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
fragment1.startPostponedEnterTransition();
@@ -294,6 +305,7 @@
.addSharedElement(startBlue1, "blueSquare")
.replace(R.id.fragmentContainer1, fragment3)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -306,6 +318,7 @@
.addSharedElement(startBlue2, "blueSquare")
.replace(R.id.fragmentContainer2, fragment4)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -358,7 +371,10 @@
@Test
public void outOfOrderContainers() throws Throwable {
final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction().remove(mBeginningFragment).commit();
+ fm.beginTransaction()
+ .remove(mBeginningFragment)
+ .setAllowOptimization(true)
+ .commit();
FragmentTestUtil.waitForExecution(mActivityRule);
FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
@@ -368,6 +384,7 @@
fm.beginTransaction()
.add(R.id.fragmentContainer1, fragment1)
.add(R.id.fragmentContainer2, fragment2)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
fragment1.startPostponedEnterTransition();
@@ -386,6 +403,7 @@
.addSharedElement(startBlue1, "blueSquare")
.replace(R.id.fragmentContainer1, fragment3)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -398,6 +416,7 @@
.addSharedElement(startBlue2, "blueSquare")
.replace(R.id.fragmentContainer2, fragment4)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -450,7 +469,10 @@
@Test
public void commitNowNoEffect() throws Throwable {
final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction().remove(mBeginningFragment).commit();
+ fm.beginTransaction()
+ .remove(mBeginningFragment)
+ .setAllowOptimization(true)
+ .commit();
FragmentTestUtil.waitForExecution(mActivityRule);
FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
@@ -460,6 +482,7 @@
fm.beginTransaction()
.add(R.id.fragmentContainer1, fragment1)
.add(R.id.fragmentContainer2, fragment2)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
fragment1.startPostponedEnterTransition();
@@ -480,6 +503,7 @@
.replace(R.id.fragmentContainer1, fragment3)
.add(strictFragment1, "1")
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -497,6 +521,7 @@
.replace(R.id.fragmentContainer2, fragment4)
.remove(strictFragment1)
.add(strictFragment2, "2")
+ .setAllowOptimization(true)
.commitNow();
}
});
@@ -534,6 +559,7 @@
.addSharedElement(startBlue1, "blueSquare")
.replace(R.id.fragmentContainer, fragment2)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -545,6 +571,7 @@
fm.beginTransaction()
.addSharedElement(startBlue2, "blueSquare")
.replace(R.id.fragmentContainer, fragment1)
+ .setAllowOptimization(true)
.commitNow();
}
});
@@ -562,7 +589,10 @@
@Test
public void noAccidentalRemoval() throws Throwable {
final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction().remove(mBeginningFragment).commit();
+ fm.beginTransaction()
+ .remove(mBeginningFragment)
+ .setAllowOptimization(true)
+ .commit();
FragmentTestUtil.waitForExecution(mActivityRule);
FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
@@ -570,6 +600,7 @@
fm.beginTransaction()
.add(R.id.fragmentContainer1, fragment1)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
fragment1.startPostponedEnterTransition();
@@ -580,6 +611,7 @@
// Create a postponed transaction that removes a view
fm.beginTransaction()
.replace(R.id.fragmentContainer1, fragment2)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
assertPostponedTransition(fragment1, fragment2, null);
@@ -588,6 +620,7 @@
// Create a transaction that doesn't interfere with the previously postponed one
fm.beginTransaction()
.replace(R.id.fragmentContainer2, fragment3)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -613,6 +646,7 @@
.addSharedElement(startBlue, "blueSquare")
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
@@ -646,6 +680,7 @@
fm1.beginTransaction()
.add(R.id.fragmentContainer, fragment1, "1")
.addToBackStack(null)
+ .setAllowOptimization(true)
.commit();
FragmentTestUtil.waitForExecution(mActivityRule);
diff --git a/fragment/tests/java/android/support/v4/app/TransitionFragment.java b/fragment/tests/java/android/support/v4/app/TransitionFragment.java
index fda2784..e6493c2 100644
--- a/fragment/tests/java/android/support/v4/app/TransitionFragment.java
+++ b/fragment/tests/java/android/support/v4/app/TransitionFragment.java
@@ -51,9 +51,11 @@
setSharedElementEnterTransition(sharedElementEnter);
setSharedElementReturnTransition(sharedElementReturn);
enterTransition.addListener(mListener);
+ sharedElementEnter.addListener(mListener);
reenterTransition.addListener(mListener);
exitTransition.addListener(mListener);
returnTransition.addListener(mListener);
+ sharedElementReturn.addListener(mListener);
}
@Override
diff --git a/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index affb130..4035e63 100644
--- a/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -71,6 +71,7 @@
mResultObj.sendResult(parcelListToItemList((List<Parcel>)result));
} else if (result instanceof Parcel) {
Parcel parcel = (Parcel) result;
+ parcel.setDataPosition(0);
mResultObj.sendResult(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
parcel.recycle();
} else {
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index e26a1bc..9bec2a3 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -23,6 +23,8 @@
defaultConfig {
minSdkVersion 9
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
sourceSets {
@@ -38,6 +40,11 @@
'java'
]
main.aidl.srcDirs = ['java']
+
+ androidTest.setRoot('tests')
+ androidTest.java.srcDir 'tests/src'
+ androidTest.res.srcDir 'tests/res'
+ androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
}
compileOptions {
@@ -113,4 +120,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
index a9fc7e8..de5047b 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -403,6 +403,11 @@
@Override
public Bundle getBrowserRootHints() {
+ // If EXTRA_MESSENGER_BINDER is used, mCurConnection is not null.
+ if (mCurConnection != null) {
+ return mCurConnection.rootHints == null ? null
+ : new Bundle(mCurConnection.rootHints);
+ }
return MediaBrowserServiceCompatApi24.getBrowserRootHints(mServiceObj);
}
}
diff --git a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
index 78b0c50..d835985 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -2302,7 +2302,9 @@
public void setCallback(Callback callback, Handler handler) {
MediaSessionCompatApi21.setCallback(mSessionObj,
callback == null ? null : callback.mCallbackObj, handler);
- callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
+ if (callback != null) {
+ callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
+ }
}
@Override
diff --git a/media-compat/tests/AndroidManifest.xml b/media-compat/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1216194
--- /dev/null
+++ b/media-compat/tests/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.mediacompat.test">
+
+ <uses-sdk
+ android:minSdkVersion="9"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling"/>
+
+ <application android:supportsRtl="true">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.support.v4.media.session.TestActivity" />
+ <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.mediacompat.test"/>
+
+</manifest>
diff --git a/media-compat/tests/NO_DOCS b/media-compat/tests/NO_DOCS
new file mode 100644
index 0000000..092a39c
--- /dev/null
+++ b/media-compat/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
new file mode 100644
index 0000000..455a706
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static junit.framework.Assert.fail;
+
+public class MediaSessionCompatTest {
+ @Rule
+ public ActivityTestRule<TestActivity> mActivityRule =
+ new ActivityTestRule<>(TestActivity.class);
+ Context mContext;
+ Map<String, LockedObject> results = new HashMap<>();
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void testSetNullCallback() throws Throwable {
+ initWait("testSetNullCallback");
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ MediaSessionCompat session = new MediaSessionCompat(mContext, "TEST");
+ session.setCallback(null);
+ } catch (Exception e) {
+ fail("Fail with an exception: " + e);
+ } finally {
+ setResultData("testSetNullCallback", true);
+ }
+ }
+ });
+ waitFor("testSetNullCallback");
+ }
+
+ private void initWait(String key) throws InterruptedException {
+ results.put(key, new LockedObject());
+ }
+
+ private Object[] waitFor(String key) throws InterruptedException {
+ return results.get(key).waitFor();
+ }
+
+ private void setResultData(String key, Object... args) {
+ if (results.containsKey(key)) {
+ results.get(key).set(args);
+ }
+ }
+
+ private class LockedObject {
+ private Semaphore mLock = new Semaphore(1);
+ private volatile Object[] mArgs;
+
+ public LockedObject() {
+ mLock.drainPermits();
+ }
+
+ public void set(Object... args) {
+ mArgs = args;
+ mLock.release(1);
+ }
+
+ public Object[] waitFor() throws InterruptedException {
+ mLock.tryAcquire(1, 2, TimeUnit.SECONDS);
+ return mArgs;
+ }
+ }
+}
diff --git a/media-compat/tests/src/android/support/v4/media/session/TestActivity.java b/media-compat/tests/src/android/support/v4/media/session/TestActivity.java
new file mode 100644
index 0000000..dd56467
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/session/TestActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {
+}
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java
index 76d6208..edb7493 100644
--- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java
@@ -50,8 +50,11 @@
CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout)
findViewById(R.id.collapsing_app_bar);
- if (collapsingToolbarLayout != null && displayTitle()) {
- collapsingToolbarLayout.setTitle(getTitle());
+ if (collapsingToolbarLayout != null) {
+ if (displayTitle()) {
+ collapsingToolbarLayout.setTitle(getTitle());
+ }
+ collapsingToolbarLayout.setContentScrimColor(0xFFFF00FF);
}
TextView dialog = (TextView) findViewById(R.id.textview_dialogue);
diff --git a/samples/SupportLeanbackDemos/AndroidManifest.xml b/samples/SupportLeanbackDemos/AndroidManifest.xml
index e1acf45..ea3c1ee 100644
--- a/samples/SupportLeanbackDemos/AndroidManifest.xml
+++ b/samples/SupportLeanbackDemos/AndroidManifest.xml
@@ -43,6 +43,22 @@
android:theme="@style/Theme.Example.Leanback.Details"
android:exported="true" />
+ <activity android:name="DetailsVideoActivity"
+ android:theme="@style/Theme.Example.Leanback.Details"
+ android:exported="true" />
+
+ <activity android:name="DetailsVideoSupportActivity"
+ android:theme="@style/Theme.Example.Leanback.Details"
+ android:exported="true" />
+
+ <activity android:name="DetailsCustomTitleActivity"
+ android:theme="@style/Theme.Example.Leanback.Details.CustomTitle"
+ android:exported="true" />
+
+ <activity android:name="DetailsCustomTitleSupportActivity"
+ android:theme="@style/Theme.Example.Leanback.Details.CustomTitle"
+ android:exported="true" />
+
<activity android:name="SearchDetailsActivity"
android:theme="@style/Theme.Example.Leanback.SearchDetails"
android:exported="true" />
@@ -59,6 +75,22 @@
android:theme="@style/Theme.Example.Leanback.Rows"
android:exported="true" />
+ <activity android:name="PlaybackActivity"
+ android:configChanges=
+ "screenSize|smallestScreenSize|screenLayout|orientation"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:launchMode="singleTask"
+ android:exported="true" />
+
+ <activity android:name="PlaybackSupportActivity"
+ android:configChanges=
+ "screenSize|smallestScreenSize|screenLayout|orientation"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:launchMode="singleTask"
+ android:exported="true" />
+
<activity android:name="PlaybackOverlayActivity"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
diff --git a/samples/SupportLeanbackDemos/generatev4.py b/samples/SupportLeanbackDemos/generatev4.py
index efaf0b4..c79f1b1 100755
--- a/samples/SupportLeanbackDemos/generatev4.py
+++ b/samples/SupportLeanbackDemos/generatev4.py
@@ -134,8 +134,6 @@
line = line.replace('DetailsActivity', 'DetailsSupportActivity')
line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
line = line.replace('extends Activity', 'extends FragmentActivity')
- line = line.replace('R.layout.details', 'R.layout.details_support')
- line = line.replace('R.layout.legacy_details', 'R.layout.legacy_details_support')
line = line.replace('getFragmentManager()', 'getSupportFragmentManager()')
line = line.replace('DetailsFragment', 'DetailsSupportFragment')
line = line.replace('NewDetailsFragment', 'NewDetailsSupportFragment')
@@ -143,26 +141,6 @@
file.close()
outfile.close()
-file = open('res/layout/details.xml', 'r')
-outfile = open('res/layout/details_support.xml', 'w')
-for line in file:
- line = replace_xml_head(line, "details")
- line = line.replace('com.example.android.leanback.NewDetailsFragment', 'com.example.android.leanback.NewDetailsSupportFragment')
- outfile.write(line)
-file.close()
-outfile.close()
-
-
-file = open('res/layout/legacy_details.xml', 'r')
-outfile = open('res/layout/legacy_details_support.xml', 'w')
-for line in file:
- line = replace_xml_head(line, "legacy_details")
- line = line.replace('com.example.android.leanback.DetailsFragment', 'com.example.android.leanback.DetailsSupportFragment')
- outfile.write(line)
-file.close()
-outfile.close()
-
-
file = open('src/com/example/android/leanback/SearchDetailsActivity.java', 'r')
outfile = open('src/com/example/android/leanback/SearchDetailsSupportActivity.java', 'w')
write_java_head(outfile, "SearchDetailsActivity")
@@ -300,16 +278,58 @@
file.close()
outfile.close()
+file = open('src/com/example/android/leanback/PlaybackFragment.java', 'r')
+outfile = open('src/com/example/android/leanback/PlaybackSupportFragment.java', 'w')
+write_java_head(outfile, "PlaybackFragment")
+for line in file:
+ line = line.replace('PlaybackFragment', 'PlaybackSupportFragment')
+ line = line.replace('PlaybackActivity', 'PlaybackSupportActivity')
+ outfile.write(line)
+file.close()
+outfile.close()
+
+file = open('src/com/example/android/leanback/PlaybackActivity.java', 'r')
+outfile = open('src/com/example/android/leanback/PlaybackSupportActivity.java', 'w')
+write_java_head(outfile, "PlaybackActivity")
+for line in file:
+ line = line.replace('PlaybackActivity', 'PlaybackSupportActivity')
+ line = line.replace('extends Activity', 'extends FragmentActivity')
+ line = line.replace('R.layout.playback_activity', 'R.layout.playback_activity_support')
+ line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
+ outfile.write(line)
+file.close()
+outfile.close()
+
+file = open('res/layout/playback_activity.xml', 'r')
+outfile = open('res/layout/playback_activity_support.xml', 'w')
+for line in file:
+ line = replace_xml_head(line, "playback_controls")
+ line = line.replace('com.example.android.leanback.PlaybackFragment', 'com.example.android.leanback.PlaybackSupportFragment')
+ outfile.write(line)
+file.close()
+outfile.close()
+
+
file = open('src/com/example/android/leanback/PlaybackOverlayFragment.java', 'r')
outfile = open('src/com/example/android/leanback/PlaybackOverlaySupportFragment.java', 'w')
write_java_head(outfile, "PlaybackOverlayFragment")
for line in file:
line = line.replace('PlaybackOverlayFragment', 'PlaybackOverlaySupportFragment')
+ line = line.replace('PlaybackControlHelper', 'PlaybackControlSupportHelper')
line = line.replace('PlaybackOverlayActivity', 'PlaybackOverlaySupportActivity')
- line = line.replace('PlaybackFragmentGlueHost', 'PlaybackSupportFragmentGlueHost')
- line = line.replace('VideoFragment', 'VideoSupportFragment')
- line = line.replace('PlaybackFragment', 'PlaybackSupportFragment')
+ outfile.write(line)
+file.close()
+outfile.close()
+
+
+file = open('src/com/example/android/leanback/PlaybackControlHelper.java', 'r')
+outfile = open('src/com/example/android/leanback/PlaybackControlSupportHelper.java', 'w')
+write_java_head(outfile, "PlaybackControlHelper")
+for line in file:
+ line = line.replace('PlaybackControlHelper', 'PlaybackControlSupportHelper')
+ line = line.replace('PlaybackControlGlue', 'PlaybackControlSupportGlue')
+ line = line.replace('PlaybackOverlayFragment', 'PlaybackOverlaySupportFragment')
outfile.write(line)
file.close()
outfile.close()
diff --git a/samples/SupportLeanbackDemos/res/layout/custom_title.xml b/samples/SupportLeanbackDemos/res/layout/custom_title.xml
new file mode 100644
index 0000000..4a88021
--- /dev/null
+++ b/samples/SupportLeanbackDemos/res/layout/custom_title.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.example.android.leanback.CustomTitleView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/browse_title_group"
+ android:orientation="horizontal" android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:text="@string/custom_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</com.example.android.leanback.CustomTitleView>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/layout/details.xml b/samples/SupportLeanbackDemos/res/layout/details.xml
deleted file mode 100644
index 3159f54..0000000
--- a/samples/SupportLeanbackDemos/res/layout/details.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.example.android.leanback.NewDetailsFragment"
- android:id="@+id/details_fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
-/>
diff --git a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml b/samples/SupportLeanbackDemos/res/layout/details_activity.xml
similarity index 85%
rename from samples/SupportLeanbackDemos/res/layout/legacy_details.xml
rename to samples/SupportLeanbackDemos/res/layout/details_activity.xml
index 4af4e6a..7fc1400 100644
--- a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml
+++ b/samples/SupportLeanbackDemos/res/layout/details_activity.xml
@@ -15,8 +15,7 @@
limitations under the License.
-->
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.example.android.leanback.DetailsFragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/details_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/samples/SupportLeanbackDemos/res/layout/legacy_details_support.xml b/samples/SupportLeanbackDemos/res/layout/legacy_details_support.xml
deleted file mode 100644
index 8146f9b..0000000
--- a/samples/SupportLeanbackDemos/res/layout/legacy_details_support.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This file is auto-generated from legacy_details.xml. DO NOT MODIFY. -->
-
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.example.android.leanback.DetailsSupportFragment"
- android:id="@+id/details_fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
-/>
diff --git a/samples/SupportLeanbackDemos/res/layout/details_support.xml b/samples/SupportLeanbackDemos/res/layout/playback_activity.xml
similarity index 66%
rename from samples/SupportLeanbackDemos/res/layout/details_support.xml
rename to samples/SupportLeanbackDemos/res/layout/playback_activity.xml
index 103dc25..91754ff 100644
--- a/samples/SupportLeanbackDemos/res/layout/details_support.xml
+++ b/samples/SupportLeanbackDemos/res/layout/playback_activity.xml
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- This file is auto-generated from details.xml. DO NOT MODIFY. -->
-
<!--
Copyright (C) 2014 The Android Open Source Project
@@ -16,10 +14,14 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.example.android.leanback.NewDetailsSupportFragment"
- android:id="@+id/details_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
-/>
+ android:layout_height="match_parent">
+
+ <fragment
+ android:id="@+id/playback_controls_fragment"
+ android:name="com.example.android.leanback.PlaybackFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/layout/details_support.xml b/samples/SupportLeanbackDemos/res/layout/playback_activity_support.xml
similarity index 61%
copy from samples/SupportLeanbackDemos/res/layout/details_support.xml
copy to samples/SupportLeanbackDemos/res/layout/playback_activity_support.xml
index 103dc25..0f2cbc1 100644
--- a/samples/SupportLeanbackDemos/res/layout/details_support.xml
+++ b/samples/SupportLeanbackDemos/res/layout/playback_activity_support.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- This file is auto-generated from details.xml. DO NOT MODIFY. -->
+<!-- This file is auto-generated from playback_controls.xml. DO NOT MODIFY. -->
<!--
Copyright (C) 2014 The Android Open Source Project
@@ -16,10 +16,14 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.example.android.leanback.NewDetailsSupportFragment"
- android:id="@+id/details_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
-/>
+ android:layout_height="match_parent">
+
+ <fragment
+ android:id="@+id/playback_controls_fragment"
+ android:name="com.example.android.leanback.PlaybackSupportFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/values/strings.xml b/samples/SupportLeanbackDemos/res/values/strings.xml
index 6a9b4a3..6de7d7b 100644
--- a/samples/SupportLeanbackDemos/res/values/strings.xml
+++ b/samples/SupportLeanbackDemos/res/values/strings.xml
@@ -29,18 +29,31 @@
<string name="search_support_description">SearchSupportFragment test</string>
<string name="details">Details</string>
<string name="details_description">DetailsFragment test</string>
+ <string name="details_video">Details with Video</string>
+ <string name="details_video_description">DetailsFragment with Video test</string>
+ <string name="details_custom_title">Details with custom title</string>
+ <string name="details_custom_title_description">DetailsFragment with custom title test</string>
<string name="details_support">Details(support version)</string>
<string name="details_support_description">DetailsSupportFragment test</string>
+ <string name="details_video_support">Details with Video(support version)</string>
+ <string name="details_video_support_description">DetailsFragment with Video test(support version)</string>
+ <string name="details_custom_title_support">Details with custom title(support version)</string>
+ <string name="details_custom_title_support_description">DetailsSupportFragment with custom title test</string>
+ <string name="custom_title">Custom Title</string>
<string name="search_details">Search Details</string>
<string name="search_details_description">Search style DetailsFragment test</string>
<string name="search_details_support">Search Details(support version)</string>
<string name="search_details_support_description">Search style DetailsSupportFragment test</string>
- <string name="playback">Playback</string>
<string name="video_playback">Video Playback</string>
<string name="video_playback_support">Video Playback(support version)</string>
- <string name="playback_description">PlaybackOverlayFragment test</string>
+ <string name="playback">Playback</string>
+ <string name="playback_description">PlaybackFragment test</string>
<string name="playback_support">Playback(support version)</string>
- <string name="playback_support_description">PlaybackOverlaySupportFragment test</string>
+ <string name="playback_support_description">PlaybackSupportFragment test</string>
+ <string name="playbackoverlay">PlaybackOverlay</string>
+ <string name="playbackoverlay_description">PlaybackOverlayFragment test</string>
+ <string name="playbackoverlay_support">PlaybackOverlay(support version)</string>
+ <string name="playbackoverlay_support_description">PlaybackOverlaySupportFragment test</string>
<string name="hgrid">Horizontal Grid</string>
<string name="hgrid_description">HorizontalGridView test</string>
<string name="vgrid">Vertical Grid</string>
diff --git a/samples/SupportLeanbackDemos/res/values/themes.xml b/samples/SupportLeanbackDemos/res/values/themes.xml
index 458c5c0..58ae373 100644
--- a/samples/SupportLeanbackDemos/res/values/themes.xml
+++ b/samples/SupportLeanbackDemos/res/values/themes.xml
@@ -23,6 +23,9 @@
</style>
<style name="Theme.Example.Leanback.Details" parent="Theme.Leanback.Details">
</style>
+ <style name="Theme.Example.Leanback.Details.CustomTitle">
+ <item name="browseTitleViewLayout">@layout/custom_title</item>.
+ </style>
<style name="Theme.Example.Leanback.VerticalGrid" parent="Theme.Leanback.VerticalGrid">
</style>
<style name="Theme.Example.Leanback.Rows" parent="Theme.Leanback">
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
index 1a8ab1f..fa1425b 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
@@ -17,6 +17,7 @@
package com.example.android.leanback;
import android.support.v4.app.Fragment;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/CustomTitleView.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/CustomTitleView.java
new file mode 100644
index 0000000..3ceb17d
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/CustomTitleView.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+
+public class CustomTitleView extends LinearLayout implements TitleViewAdapter.Provider {
+
+ private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
+ @Override
+ public View getSearchAffordanceView() {
+ return null;
+ }
+
+ @Override
+ public void setOnSearchClickedListener(View.OnClickListener listener) {
+ }
+
+ @Override
+ public void setAnimationEnabled(boolean enable) {
+ }
+
+ @Override
+ public Drawable getBadgeDrawable() {
+ return null;
+ }
+
+ @Override
+ public SearchOrbView.Colors getSearchAffordanceColors() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Override
+ public void setBadgeDrawable(Drawable drawable) {
+ }
+
+ @Override
+ public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+ }
+
+ @Override
+ public void setTitle(CharSequence titleText) {
+ }
+
+ @Override
+ public void updateComponentsVisibility(int flags) {
+ }
+ };
+
+ public CustomTitleView(Context context) {
+ this(context, null);
+ }
+
+ public CustomTitleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public TitleViewAdapter getTitleViewAdapter() {
+ return mTitleViewAdapter;
+ }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java
index 43c6910..6e2cbb4 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java
@@ -26,22 +26,41 @@
&& !(this instanceof SearchDetailsActivity));
}
+ protected boolean hasBackgroundVideo() {
+ return false;
+ }
+
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
getFragmentManager().enableDebugLogging(true);
- setContentView(useLegacyFragment() ? R.layout.legacy_details : R.layout.details);
+ setContentView(R.layout.details_activity);
if (savedInstanceState == null) {
- // Only pass object to fragment when activity is first time created,
- // later object is modified and persisted with fragment state.
if (useLegacyFragment()) {
- ((DetailsFragment)getFragmentManager().findFragmentById(R.id.details_fragment))
- .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ DetailsFragment fragment = new DetailsFragment();
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ getFragmentManager().beginTransaction()
+ .replace(R.id.details_fragment, fragment)
+ .commit();
} else {
- ((NewDetailsFragment)getFragmentManager().findFragmentById(R.id.details_fragment))
- .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ NewDetailsFragment fragment = new NewDetailsFragment();
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ fragment.setBackgroundVideo(hasBackgroundVideo());
+ getFragmentManager().beginTransaction()
+ .replace(R.id.details_fragment, fragment)
+ .commit();
+ }
+ } else {
+ if (useLegacyFragment()) {
+ DetailsFragment fragment = (DetailsFragment) getFragmentManager()
+ .findFragmentById(R.id.details_fragment);
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ } else {
+ NewDetailsFragment fragment = (NewDetailsFragment) getFragmentManager()
+ .findFragmentById(R.id.details_fragment);
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
}
}
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleActivity.java
new file mode 100644
index 0000000..0796fb7
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+/**
+ * Same function as DetailsActivity, using different theme in AndroidManifest.
+ */
+public class DetailsCustomTitleActivity extends DetailsActivity {
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleSupportActivity.java
new file mode 100644
index 0000000..4ad2f7a
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleSupportActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+/**
+ * Same function as DetailsSupportActivity, using different theme in AndroidManifest.
+ */
+public class DetailsCustomTitleSupportActivity extends DetailsSupportActivity {
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
index 09d9526..56acc05 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
@@ -126,12 +126,7 @@
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
mRowsAdapter = new ArrayObjectAdapter(ps);
-
- PhotoItem item = (PhotoItem) (savedInstanceState != null ?
- savedInstanceState.getParcelable(ITEM) : null);
- if (item != null) {
- setItem(item);
- }
+ updateAdapter();
setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
@@ -179,7 +174,13 @@
public void setItem(PhotoItem photoItem) {
mPhotoItem = photoItem;
+ updateAdapter();
+ }
+ void updateAdapter() {
+ if (mRowsAdapter == null) {
+ return;
+ }
mRowsAdapter.clear();
new Handler().postDelayed(new Runnable() {
public void run() {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java
index 3ebf101..48d15e5 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java
@@ -29,22 +29,41 @@
&& !(this instanceof SearchDetailsSupportActivity));
}
+ protected boolean hasBackgroundVideo() {
+ return false;
+ }
+
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
getSupportFragmentManager().enableDebugLogging(true);
- setContentView(useLegacyFragment() ? R.layout.legacy_details_support : R.layout.details_support);
+ setContentView(R.layout.details_activity);
if (savedInstanceState == null) {
- // Only pass object to fragment when activity is first time created,
- // later object is modified and persisted with fragment state.
if (useLegacyFragment()) {
- ((DetailsSupportFragment)getSupportFragmentManager().findFragmentById(R.id.details_fragment))
- .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ DetailsSupportFragment fragment = new DetailsSupportFragment();
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.details_fragment, fragment)
+ .commit();
} else {
- ((NewDetailsSupportFragment)getSupportFragmentManager().findFragmentById(R.id.details_fragment))
- .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ NewDetailsSupportFragment fragment = new NewDetailsSupportFragment();
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ fragment.setBackgroundVideo(hasBackgroundVideo());
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.details_fragment, fragment)
+ .commit();
+ }
+ } else {
+ if (useLegacyFragment()) {
+ DetailsSupportFragment fragment = (DetailsSupportFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.details_fragment);
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+ } else {
+ NewDetailsSupportFragment fragment = (NewDetailsSupportFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.details_fragment);
+ fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
}
}
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
index 28a61d9..e58e2e7 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
@@ -129,12 +129,7 @@
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
mRowsAdapter = new ArrayObjectAdapter(ps);
-
- PhotoItem item = (PhotoItem) (savedInstanceState != null ?
- savedInstanceState.getParcelable(ITEM) : null);
- if (item != null) {
- setItem(item);
- }
+ updateAdapter();
setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
@@ -182,7 +177,13 @@
public void setItem(PhotoItem photoItem) {
mPhotoItem = photoItem;
+ updateAdapter();
+ }
+ void updateAdapter() {
+ if (mRowsAdapter == null) {
+ return;
+ }
mRowsAdapter.clear();
new Handler().postDelayed(new Runnable() {
public void run() {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoActivity.java
new file mode 100644
index 0000000..06b65b7
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+public class DetailsVideoActivity extends DetailsActivity {
+
+ @Override
+ protected boolean hasBackgroundVideo() {
+ return true;
+ }
+
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoSupportActivity.java
new file mode 100644
index 0000000..0c31d36
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoSupportActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+public class DetailsVideoSupportActivity extends DetailsSupportActivity {
+
+ @Override
+ protected boolean hasBackgroundVideo() {
+ return true;
+ }
+
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
index eab5bbe..fcd8e65 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
@@ -26,6 +26,7 @@
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.res.ResourcesCompat;
+
import java.util.List;
/**
@@ -60,12 +61,35 @@
R.string.browse_support_description);
addAction(actions, SearchActivity.class, R.string.search, R.string.search_description);
addAction(actions, SearchSupportActivity.class, R.string.search_support, R.string.search_support_description);
- addAction(actions, DetailsActivity.class, R.string.details, R.string.details_description);
+
+ addAction(actions, DetailsActivity.class, R.string.details,
+ R.string.details_description);
actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
new PhotoItem("Hello world", R.drawable.gallery_photo_1));
- addAction(actions, DetailsSupportActivity.class, R.string.details_support, R.string.details_support_description);
+ addAction(actions, DetailsSupportActivity.class, R.string.details_support,
+ R.string.details_support_description);
actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+
+ addAction(actions, DetailsVideoActivity.class, R.string.details_video,
+ R.string.details_video_description);
+ actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+ new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+ addAction(actions, DetailsVideoSupportActivity.class, R.string.details_video_support,
+ R.string.details_video_support_description);
+ actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+ new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+
+ addAction(actions, DetailsCustomTitleActivity.class, R.string.details_custom_title,
+ R.string.details_custom_title_description);
+ actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+ new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+ addAction(actions, DetailsCustomTitleSupportActivity.class,
+ R.string.details_custom_title_support,
+ R.string.details_custom_title_support_description);
+ actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+ new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+
addAction(actions, SearchDetailsActivity.class, R.string.search_details,
R.string.search_details_description);
actions.get(actions.size()-1).getIntent().putExtra(SearchDetailsActivity.EXTRA_ITEM,
@@ -91,10 +115,14 @@
R.string.browseerror_description);
addAction(actions, BrowseErrorSupportActivity.class, R.string.browseerror_support,
R.string.browseerror_support_description);
- addAction(actions, PlaybackOverlayActivity.class, R.string.playback,
+ addAction(actions, PlaybackActivity.class, R.string.playback,
R.string.playback_description);
- addAction(actions, PlaybackOverlaySupportActivity.class, R.string.playback_support,
+ addAction(actions, PlaybackSupportActivity.class, R.string.playback_support,
R.string.playback_support_description);
+ addAction(actions, PlaybackOverlayActivity.class, R.string.playbackoverlay,
+ R.string.playbackoverlay_description);
+ addAction(actions, PlaybackOverlaySupportActivity.class,
+ R.string.playbackoverlay_support, R.string.playbackoverlay_support_description);
addAction(actions, VideoActivity.class, R.string.video_playback,
R.string.playback_description);
addAction(actions, VideoSupportActivity.class, R.string.video_playback_support,
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
index 7803c29..395d557 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
@@ -20,8 +20,7 @@
import android.os.Handler;
import android.support.v17.leanback.app.DetailsBackgroundParallaxHelper;
import android.support.v17.leanback.app.DetailsFragmentVideoHelper;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.VideoFragment;
+import android.support.v17.leanback.media.MediaPlayerGlue;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
@@ -63,6 +62,7 @@
private boolean TEST_OVERVIEW_ROW_ON_SECOND;
private boolean TEST_SHARED_ELEMENT_TRANSITION;
private boolean TEST_ENTRANCE_TRANSITION;
+ private boolean TEST_BACKGROUND_PLAYER;
private static final long TIME_TO_LOAD_OVERVIEW_ROW_MS = 1000;
private static final long TIME_TO_LOAD_RELATED_ROWS_MS = 2000;
@@ -77,7 +77,6 @@
private BackgroundHelper mBackgroundHelper;
private int mBitmapMinVerticalOffset = -100;
private MediaPlayerGlue mMediaPlayerGlue;
- private VideoFragment mVideoFragment;
private void initializeTest() {
TEST_SHARED_ELEMENT_TRANSITION = null != getActivity().getWindow()
@@ -97,27 +96,32 @@
getActivity(), getParallaxManager())
.setCoverImageMinVerticalOffset(mBitmapMinVerticalOffset)
.build();
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
- mMediaPlayerGlue.setHost(createPlaybackGlueHost());
- mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
- mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
+ if (TEST_BACKGROUND_PLAYER) {
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
+ mMediaPlayerGlue.setHost(createPlaybackGlueHost());
+ mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
+ mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
- mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
- mMediaPlayerGlue.setArtist("A Googleer");
- mMediaPlayerGlue.setTitle("Diving with Sharks");
- mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+ mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+ mMediaPlayerGlue.setArtist("A Googleer");
+ mMediaPlayerGlue.setTitle("Diving with Sharks");
+ mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+
+ }
final Context context = getActivity();
setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
context.getTheme()));
setTitle("Leanback Sample App");
- setOnSearchClickedListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(getActivity(), SearchActivity.class);
- startActivity(intent);
- }
- });
+ if (!TEST_BACKGROUND_PLAYER) {
+ setOnSearchClickedListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(getActivity(), SearchActivity.class);
+ startActivity(intent);
+ }
+ });
+ }
mActionPlay = new Action(ACTION_PLAY, "Play");
mActionRent = new Action(ACTION_RENT, "Rent", "$3.99", ResourcesCompat.getDrawable(
@@ -165,12 +169,7 @@
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
mRowsAdapter = new ArrayObjectAdapter(ps);
-
- PhotoItem item = (PhotoItem) (savedInstanceState != null ?
- savedInstanceState.getParcelable(ITEM) : null);
- if (item != null) {
- setItem(item);
- }
+ updateAdapter();
setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
@@ -223,9 +222,19 @@
return view;
}
+ public void setBackgroundVideo(boolean backgroundVideo) {
+ TEST_BACKGROUND_PLAYER = backgroundVideo;
+ }
+
public void setItem(PhotoItem photoItem) {
mPhotoItem = photoItem;
+ updateAdapter();
+ }
+ void updateAdapter() {
+ if (mRowsAdapter == null) {
+ return;
+ }
mRowsAdapter.clear();
new Handler().postDelayed(new Runnable() {
public void run() {
@@ -295,6 +304,8 @@
@Override
public void onStop() {
super.onStop();
- mMediaPlayerGlue.pause();
+ if (TEST_BACKGROUND_PLAYER) {
+ mMediaPlayerGlue.pause();
+ }
}
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
index 8f0172f..ba482b1 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
@@ -23,8 +23,7 @@
import android.os.Handler;
import android.support.v17.leanback.app.DetailsBackgroundParallaxHelper;
import android.support.v17.leanback.app.DetailsFragmentVideoHelper;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.VideoSupportFragment;
+import android.support.v17.leanback.media.MediaPlayerGlue;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
@@ -66,6 +65,7 @@
private boolean TEST_OVERVIEW_ROW_ON_SECOND;
private boolean TEST_SHARED_ELEMENT_TRANSITION;
private boolean TEST_ENTRANCE_TRANSITION;
+ private boolean TEST_BACKGROUND_PLAYER;
private static final long TIME_TO_LOAD_OVERVIEW_ROW_MS = 1000;
private static final long TIME_TO_LOAD_RELATED_ROWS_MS = 2000;
@@ -80,7 +80,6 @@
private BackgroundHelper mBackgroundHelper;
private int mBitmapMinVerticalOffset = -100;
private MediaPlayerGlue mMediaPlayerGlue;
- private VideoSupportFragment mVideoSupportFragment;
private void initializeTest() {
TEST_SHARED_ELEMENT_TRANSITION = null != getActivity().getWindow()
@@ -100,27 +99,32 @@
getActivity(), getParallaxManager())
.setCoverImageMinVerticalOffset(mBitmapMinVerticalOffset)
.build();
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
- mMediaPlayerGlue.setHost(createPlaybackGlueHost());
- mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
- mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
+ if (TEST_BACKGROUND_PLAYER) {
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
+ mMediaPlayerGlue.setHost(createPlaybackGlueHost());
+ mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
+ mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
- mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
- mMediaPlayerGlue.setArtist("A Googleer");
- mMediaPlayerGlue.setTitle("Diving with Sharks");
- mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+ mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+ mMediaPlayerGlue.setArtist("A Googleer");
+ mMediaPlayerGlue.setTitle("Diving with Sharks");
+ mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+
+ }
final Context context = getActivity();
setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
context.getTheme()));
setTitle("Leanback Sample App");
- setOnSearchClickedListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(getActivity(), SearchSupportActivity.class);
- startActivity(intent);
- }
- });
+ if (!TEST_BACKGROUND_PLAYER) {
+ setOnSearchClickedListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(getActivity(), SearchSupportActivity.class);
+ startActivity(intent);
+ }
+ });
+ }
mActionPlay = new Action(ACTION_PLAY, "Play");
mActionRent = new Action(ACTION_RENT, "Rent", "$3.99", ResourcesCompat.getDrawable(
@@ -168,12 +172,7 @@
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
mRowsAdapter = new ArrayObjectAdapter(ps);
-
- PhotoItem item = (PhotoItem) (savedInstanceState != null ?
- savedInstanceState.getParcelable(ITEM) : null);
- if (item != null) {
- setItem(item);
- }
+ updateAdapter();
setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
@@ -226,9 +225,19 @@
return view;
}
+ public void setBackgroundVideo(boolean backgroundVideo) {
+ TEST_BACKGROUND_PLAYER = backgroundVideo;
+ }
+
public void setItem(PhotoItem photoItem) {
mPhotoItem = photoItem;
+ updateAdapter();
+ }
+ void updateAdapter() {
+ if (mRowsAdapter == null) {
+ return;
+ }
mRowsAdapter.clear();
new Handler().postDelayed(new Runnable() {
public void run() {
@@ -298,6 +307,8 @@
@Override
public void onStop() {
super.onStop();
- mMediaPlayerGlue.pause();
+ if (TEST_BACKGROUND_PLAYER) {
+ mMediaPlayerGlue.pause();
+ }
}
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackActivity.java
new file mode 100644
index 0000000..14a13cd
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Host PlaybackFragment and provide PIP events.
+ */
+public class PlaybackActivity extends Activity {
+ private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.playback_activity);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ for (PictureInPictureListener listener : mListeners) {
+ listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+
+ /**
+ * Register a PIP listener.
+ */
+ public void registerPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Unregister a PIP listener.
+ */
+ public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Interface of PIP event on Activity.
+ */
+ public interface PictureInPictureListener {
+ /**
+ * Called when Activity's PIP mode is changed.
+ */
+ void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+ }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlGlue.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlGlue.java
new file mode 100644
index 0000000..e04fa46
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlGlue.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+ /**
+ * Change the location of the thumbs up/down controls
+ */
+ private static final boolean THUMBS_PRIMARY = true;
+
+ private static final String FAUX_TITLE = "A short song of silence";
+ private static final String FAUX_SUBTITLE = "2014";
+ private static final int FAUX_DURATION = 33 * 1000;
+
+ // These should match the playback service FF behavior
+ private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+ private boolean mIsPlaying;
+ private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
+ private long mStartTime;
+ private long mStartPosition = 0;
+
+ private PlaybackControlsRow.RepeatAction mRepeatAction;
+ private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+ private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+ private PlaybackControlsRow.PictureInPictureAction mPipAction;
+
+ private Handler mHandler = new Handler();
+ private final Runnable mUpdateProgressRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateProgress();
+ mHandler.postDelayed(this, getUpdatePeriod());
+ }
+ };
+
+ PlaybackControlGlue(Context context) {
+ super(context, sFastForwardSpeeds);
+ mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+ mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+ mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+ mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+ }
+
+ @Override
+ protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
+ if (!THUMBS_PRIMARY) {
+ adapter.add(mThumbsDownAction);
+ }
+ if (android.os.Build.VERSION.SDK_INT > 23) {
+ adapter.add(mPipAction);
+ }
+ adapter.add(mRepeatAction);
+ if (!THUMBS_PRIMARY) {
+ adapter.add(mThumbsUpAction);
+ }
+ }
+
+ @Override
+ protected void onCreatePrimaryActions(SparseArrayObjectAdapter adapter) {
+ if (THUMBS_PRIMARY) {
+ adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+ adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+ }
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return true;
+ }
+ }
+ return super.onKey(view, keyCode, keyEvent);
+ }
+
+ private boolean shouldDispatchAction(Action action) {
+ return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
+ }
+
+ private void dispatchAction(Action action) {
+ Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+ PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+ multiAction.nextIndex();
+ notifyActionChanged(multiAction);
+ }
+
+ private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+ int index;
+ index = getPrimaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ } else {
+ index = getSecondaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ }
+ }
+ }
+
+ private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+ return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+ }
+
+ private ArrayObjectAdapter getSecondaryActionsAdapter() {
+ return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+ }
+
+ @Override
+ public boolean hasValidMedia() {
+ return true;
+ }
+
+ @Override
+ public boolean isMediaPlaying() {
+ return mIsPlaying;
+ }
+
+ @Override
+ public CharSequence getMediaTitle() {
+ return FAUX_TITLE;
+ }
+
+ @Override
+ public CharSequence getMediaSubtitle() {
+ return FAUX_SUBTITLE;
+ }
+
+ @Override
+ public int getMediaDuration() {
+ return FAUX_DURATION;
+ }
+
+ @Override
+ public Drawable getMediaArt() {
+ return null;
+ }
+
+ @Override
+ public long getSupportedActions() {
+ return PlaybackControlGlue.ACTION_PLAY_PAUSE
+ | PlaybackControlGlue.ACTION_FAST_FORWARD
+ | PlaybackControlGlue.ACTION_REWIND;
+ }
+
+ @Override
+ public int getCurrentSpeedId() {
+ return mSpeed;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ int speed;
+ if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+ speed = 0;
+ } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+ speed = 1;
+ } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = getFastForwardSpeeds()[index];
+ } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = -getRewindSpeeds()[index];
+ } else {
+ return -1;
+ }
+ long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+ if (position > getMediaDuration()) {
+ position = getMediaDuration();
+ onPlaybackComplete(true);
+ } else if (position < 0) {
+ position = 0;
+ onPlaybackComplete(false);
+ }
+ return (int) position;
+ }
+
+ void onPlaybackComplete(final boolean ended) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+ pause();
+ } else {
+ play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+ }
+ mStartPosition = 0;
+ onStateChanged();
+ }
+ });
+ }
+
+ @Override
+ public void play(int speed) {
+ if (speed == mSpeed) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = speed;
+ mIsPlaying = true;
+ mStartTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void pause() {
+ if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
+ mIsPlaying = false;
+ }
+
+ @Override
+ public void next() {
+ // Not supported
+ }
+
+ @Override
+ public void previous() {
+ // Not supported
+ }
+
+ @Override
+ public void enableProgressUpdating(boolean enable) {
+ mHandler.removeCallbacks(mUpdateProgressRunnable);
+ if (enable) {
+ mUpdateProgressRunnable.run();
+ }
+ }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java
index 308c7f4..a6752eb 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java
@@ -19,9 +19,7 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.support.v17.leanback.app.MediaPlayerGlue;
import android.support.v17.leanback.app.PlaybackControlGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
@@ -33,7 +31,7 @@
import android.view.View;
import android.widget.Toast;
-abstract class PlaybackControlHelper extends MediaPlayerGlue {
+abstract class PlaybackControlHelper extends PlaybackControlGlue {
/**
* Change the location of the thumbs up/down controls
*/
@@ -56,17 +54,20 @@
private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
private PlaybackControlsRow.PictureInPictureAction mPipAction;
- private static Handler sHandler = new Handler();
+ private Handler mHandler = new Handler();
+ // simulating whether the media is yet prepared and ready to play
+ private boolean mInitialized = true;
+
private final Runnable mUpdateProgressRunnable = new Runnable() {
@Override
public void run() {
updateProgress();
- sHandler.postDelayed(this, getUpdatePeriod());
+ mHandler.postDelayed(this, getUpdatePeriod());
}
};
- public PlaybackControlHelper(Context context, PlaybackGlue.PlaybackGlueHost host) {
- super(context, host, sFastForwardSpeeds, sFastForwardSpeeds);
+ PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment) {
+ super(context, fragment, sFastForwardSpeeds);
mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
@@ -161,7 +162,7 @@
@Override
public boolean hasValidMedia() {
- return true;
+ return mInitialized;
}
@Override
@@ -181,7 +182,7 @@
@Override
public int getMediaDuration() {
- return FAUX_DURATION;
+ return mInitialized ? FAUX_DURATION : 0;
}
@Override
@@ -230,7 +231,7 @@
}
void onPlaybackComplete(final boolean ended) {
- sHandler.post(new Runnable() {
+ mHandler.post(new Runnable() {
@Override
public void run() {
if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
@@ -277,9 +278,21 @@
@Override
public void enableProgressUpdating(boolean enable) {
- sHandler.removeCallbacks(mUpdateProgressRunnable);
+ mHandler.removeCallbacks(mUpdateProgressRunnable);
if (enable) {
mUpdateProgressRunnable.run();
}
}
-}
+
+ public boolean isInitialized() {
+ return mInitialized;
+ }
+
+ public void setInitialized(boolean initialized) {
+ if (mInitialized != initialized) {
+ mInitialized = initialized;
+ onMetadataChanged();
+ onStateChanged();
+ }
+ }
+};
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlSupportHelper.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlSupportHelper.java
new file mode 100644
index 0000000..0425e60
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlSupportHelper.java
@@ -0,0 +1,301 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackControlHelper.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.support.v17.leanback.app.PlaybackControlSupportGlue;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+abstract class PlaybackControlSupportHelper extends PlaybackControlSupportGlue {
+ /**
+ * Change the location of the thumbs up/down controls
+ */
+ private static final boolean THUMBS_PRIMARY = true;
+
+ private static final String FAUX_TITLE = "A short song of silence";
+ private static final String FAUX_SUBTITLE = "2014";
+ private static final int FAUX_DURATION = 33 * 1000;
+
+ // These should match the playback service FF behavior
+ private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+ private boolean mIsPlaying;
+ private int mSpeed = PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED;
+ private long mStartTime;
+ private long mStartPosition = 0;
+
+ private PlaybackControlsRow.RepeatAction mRepeatAction;
+ private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+ private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+ private PlaybackControlsRow.PictureInPictureAction mPipAction;
+
+ private Handler mHandler = new Handler();
+ // simulating whether the media is yet prepared and ready to play
+ private boolean mInitialized = true;
+
+ private final Runnable mUpdateProgressRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateProgress();
+ mHandler.postDelayed(this, getUpdatePeriod());
+ }
+ };
+
+ PlaybackControlSupportHelper(Context context, PlaybackOverlaySupportFragment fragment) {
+ super(context, fragment, sFastForwardSpeeds);
+ mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+ mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+ mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+ mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+ }
+
+ @Override
+ public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+ PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
+
+ ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
+ getControlsRow().setSecondaryActionsAdapter(adapter);
+ if (!THUMBS_PRIMARY) {
+ adapter.add(mThumbsDownAction);
+ }
+ if (android.os.Build.VERSION.SDK_INT > 23) {
+ adapter.add(mPipAction);
+ }
+ adapter.add(mRepeatAction);
+ if (!THUMBS_PRIMARY) {
+ adapter.add(mThumbsUpAction);
+ }
+
+ return presenter;
+ }
+
+ @Override
+ protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+ PresenterSelector presenterSelector) {
+ SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+ if (THUMBS_PRIMARY) {
+ adapter.set(PlaybackControlSupportGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+ adapter.set(PlaybackControlSupportGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+ }
+ return adapter;
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return true;
+ }
+ }
+ return super.onKey(view, keyCode, keyEvent);
+ }
+
+ private boolean shouldDispatchAction(Action action) {
+ return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
+ }
+
+ private void dispatchAction(Action action) {
+ Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+ PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+ multiAction.nextIndex();
+ notifyActionChanged(multiAction);
+ }
+
+ private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+ int index;
+ index = getPrimaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ } else {
+ index = getSecondaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ }
+ }
+ }
+
+ private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+ return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+ }
+
+ private ArrayObjectAdapter getSecondaryActionsAdapter() {
+ return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+ }
+
+ @Override
+ public boolean hasValidMedia() {
+ return mInitialized;
+ }
+
+ @Override
+ public boolean isMediaPlaying() {
+ return mIsPlaying;
+ }
+
+ @Override
+ public CharSequence getMediaTitle() {
+ return FAUX_TITLE;
+ }
+
+ @Override
+ public CharSequence getMediaSubtitle() {
+ return FAUX_SUBTITLE;
+ }
+
+ @Override
+ public int getMediaDuration() {
+ return mInitialized ? FAUX_DURATION : 0;
+ }
+
+ @Override
+ public Drawable getMediaArt() {
+ return null;
+ }
+
+ @Override
+ public long getSupportedActions() {
+ return PlaybackControlSupportGlue.ACTION_PLAY_PAUSE |
+ PlaybackControlSupportGlue.ACTION_FAST_FORWARD |
+ PlaybackControlSupportGlue.ACTION_REWIND;
+ }
+
+ @Override
+ public int getCurrentSpeedId() {
+ return mSpeed;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ int speed;
+ if (mSpeed == PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED) {
+ speed = 0;
+ } else if (mSpeed == PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL) {
+ speed = 1;
+ } else if (mSpeed >= PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = mSpeed - PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = getFastForwardSpeeds()[index];
+ } else if (mSpeed <= -PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = -mSpeed - PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = -getRewindSpeeds()[index];
+ } else {
+ return -1;
+ }
+ long position = mStartPosition +
+ (System.currentTimeMillis() - mStartTime) * speed;
+ if (position > getMediaDuration()) {
+ position = getMediaDuration();
+ onPlaybackComplete(true);
+ } else if (position < 0) {
+ position = 0;
+ onPlaybackComplete(false);
+ }
+ return (int) position;
+ }
+
+ void onPlaybackComplete(final boolean ended) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+ pausePlayback();
+ } else {
+ startPlayback(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL);
+ }
+ mStartPosition = 0;
+ onStateChanged();
+ }
+ });
+ }
+
+ @Override
+ protected void startPlayback(int speed) {
+ if (speed == mSpeed) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = speed;
+ mIsPlaying = true;
+ mStartTime = System.currentTimeMillis();
+ }
+
+ @Override
+ protected void pausePlayback() {
+ if (mSpeed == PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED;
+ mIsPlaying = false;
+ }
+
+ @Override
+ protected void skipToNext() {
+ // Not supported
+ }
+
+ @Override
+ protected void skipToPrevious() {
+ // Not supported
+ }
+
+ @Override
+ public void enableProgressUpdating(boolean enable) {
+ mHandler.removeCallbacks(mUpdateProgressRunnable);
+ if (enable) {
+ mUpdateProgressRunnable.run();
+ }
+ }
+
+ public boolean isInitialized() {
+ return mInitialized;
+ }
+
+ public void setInitialized(boolean initialized) {
+ if (mInitialized != initialized) {
+ mInitialized = initialized;
+ onMetadataChanged();
+ onStateChanged();
+ }
+ }
+};
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackFragment.java
new file mode 100644
index 0000000..a29a995
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackFragment.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+
+/**
+ * Example of PlaybackFragment working with a PlaybackControlGlue.
+ */
+public class PlaybackFragment
+ extends android.support.v17.leanback.app.PlaybackFragment
+ implements PlaybackActivity.PictureInPictureListener {
+ private static final String TAG = "leanback.PlaybackControlsFragment";
+
+ /**
+ * Change this to choose a different overlay background.
+ */
+ private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+ /**
+ * Change the number of related content rows.
+ */
+ private static final int RELATED_CONTENT_ROWS = 3;
+
+ /**
+ * Change this to select hidden
+ */
+ private static final boolean SECONDARY_HIDDEN = false;
+
+ private static final int ROW_CONTROLS = 0;
+
+ private PlaybackControlGlue mGlue;
+ private ListRowPresenter mListRowPresenter;
+
+ public SparseArrayObjectAdapter getAdapter() {
+ return (SparseArrayObjectAdapter) super.getAdapter();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ setBackgroundType(BACKGROUND_TYPE);
+
+ createComponents(getActivity());
+ }
+
+ private void createComponents(Context context) {
+ mGlue = new PlaybackControlGlue(context) {
+ @Override
+ public int getUpdatePeriod() {
+ int totalTime = getControlsRow().getTotalTime();
+ if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+ return 1000;
+ }
+ return Math.max(16, totalTime / getView().getWidth());
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == R.id.lb_control_picture_in_picture) {
+ getActivity().enterPictureInPictureMode();
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ protected void onCreateControlsRowAndPresenter() {
+ super.onCreateControlsRowAndPresenter();
+ getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+ }
+ };
+
+ mGlue.setHost(new PlaybackFragmentGlueHost(this));
+ mListRowPresenter = new ListRowPresenter();
+
+ setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+ @Override
+ public Presenter getPresenter(Object object) {
+ if (object instanceof PlaybackControlsRow) {
+ return mGlue.getControlsRowPresenter();
+ } else if (object instanceof ListRow) {
+ return mListRowPresenter;
+ }
+ throw new IllegalArgumentException("Unhandled object: " + object);
+ }
+ }));
+
+ // Add the controls row
+ getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+ // Add related content rows
+ for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+ listRowAdapter.add("Some related content");
+ listRowAdapter.add("Other related content");
+ HeaderItem header = new HeaderItem(i, "Row " + i);
+ getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ ((PlaybackActivity) getActivity()).registerPictureInPictureListener(this);
+ }
+
+ @Override
+ public void onStop() {
+ ((PlaybackActivity) getActivity()).unregisterPictureInPictureListener(this);
+ super.onStop();
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ if (isInPictureInPictureMode) {
+ // Hide the controls in picture-in-picture mode.
+ setFadingEnabled(true);
+ fadeOut();
+ } else {
+ setFadingEnabled(mGlue.isMediaPlaying());
+ }
+ }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java
index 06ec502..bca057f 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java
@@ -15,30 +15,32 @@
import android.content.Context;
import android.os.Bundle;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.PlaybackFragment;
-import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
+import android.os.Handler;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.util.Log;
public class PlaybackOverlayFragment
- extends android.support.v17.leanback.app.PlaybackFragment
+ extends android.support.v17.leanback.app.PlaybackOverlayFragment
implements PlaybackOverlayActivity.PictureInPictureListener {
private static final String TAG = "leanback.PlaybackControlsFragment";
/**
* Change this to choose a different overlay background.
*/
- private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+ private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
/**
* Change the number of related content rows.
@@ -55,6 +57,31 @@
private PlaybackControlHelper mGlue;
private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter;
private ListRowPresenter mListRowPresenter;
+ final Handler mHandler = new Handler();
+
+ // Artificial delay to simulate a media being prepared. The onRowChanged callback should be
+ // called and the playback row UI should be updated after this delay.
+ private static final int MEDIA_PREPARATION_DELAY = 500;
+
+ private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemClicked: " + item + " row " + row);
+ if (item instanceof Action) {
+ mGlue.onActionClicked((Action) item);
+ }
+ }
+ };
+
+ private OnItemViewSelectedListener mOnItemViewSelectedListener =
+ new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemSelected: " + item + " row " + row);
+ }
+ };
public SparseArrayObjectAdapter getAdapter() {
return (SparseArrayObjectAdapter) super.getAdapter();
@@ -66,12 +93,13 @@
super.onCreate(savedInstanceState);
setBackgroundType(BACKGROUND_TYPE);
+ setOnItemViewSelectedListener(mOnItemViewSelectedListener);
createComponents(getActivity());
}
private void createComponents(Context context) {
- mGlue = new PlaybackControlHelper(context, new PlaybackFragmentGlueHost(this)) {
+ mGlue = new PlaybackControlHelper(context, this) {
@Override
public int getUpdatePeriod() {
int totalTime = getControlsRow().getTotalTime();
@@ -102,6 +130,15 @@
}
};
+ mGlue.setInitialized(false);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mGlue.setInitialized(true);
+ }
+ }, MEDIA_PREPARATION_DELAY);
+ mGlue.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
mPlaybackControlsRowPresenter = mGlue.createControlsRowAndPresenter();
mPlaybackControlsRowPresenter.setSecondaryActionsHidden(SECONDARY_HIDDEN);
mListRowPresenter = new ListRowPresenter();
@@ -129,13 +166,14 @@
HeaderItem header = new HeaderItem(i, "Row " + i);
getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
}
+
}
@Override
public void onStart() {
super.onStart();
mGlue.setFadingEnabled(true);
- mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
+ mGlue.enableProgressUpdating(true);
((PlaybackOverlayActivity) getActivity()).registerPictureInPictureListener(this);
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java
index c7dc7b7..b806a9a 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java
@@ -18,30 +18,32 @@
import android.content.Context;
import android.os.Bundle;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.PlaybackSupportFragment;
-import android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost;
+import android.os.Handler;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.util.Log;
public class PlaybackOverlaySupportFragment
- extends android.support.v17.leanback.app.PlaybackSupportFragment
+ extends android.support.v17.leanback.app.PlaybackOverlaySupportFragment
implements PlaybackOverlaySupportActivity.PictureInPictureListener {
private static final String TAG = "leanback.PlaybackControlsFragment";
/**
* Change this to choose a different overlay background.
*/
- private static final int BACKGROUND_TYPE = PlaybackSupportFragment.BG_LIGHT;
+ private static final int BACKGROUND_TYPE = PlaybackOverlaySupportFragment.BG_LIGHT;
/**
* Change the number of related content rows.
@@ -55,9 +57,34 @@
private static final int ROW_CONTROLS = 0;
- private PlaybackControlHelper mGlue;
+ private PlaybackControlSupportHelper mGlue;
private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter;
private ListRowPresenter mListRowPresenter;
+ final Handler mHandler = new Handler();
+
+ // Artificial delay to simulate a media being prepared. The onRowChanged callback should be
+ // called and the playback row UI should be updated after this delay.
+ private static final int MEDIA_PREPARATION_DELAY = 500;
+
+ private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemClicked: " + item + " row " + row);
+ if (item instanceof Action) {
+ mGlue.onActionClicked((Action) item);
+ }
+ }
+ };
+
+ private OnItemViewSelectedListener mOnItemViewSelectedListener =
+ new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemSelected: " + item + " row " + row);
+ }
+ };
public SparseArrayObjectAdapter getAdapter() {
return (SparseArrayObjectAdapter) super.getAdapter();
@@ -69,12 +96,13 @@
super.onCreate(savedInstanceState);
setBackgroundType(BACKGROUND_TYPE);
+ setOnItemViewSelectedListener(mOnItemViewSelectedListener);
createComponents(getActivity());
}
private void createComponents(Context context) {
- mGlue = new PlaybackControlHelper(context, new PlaybackSupportFragmentGlueHost(this)) {
+ mGlue = new PlaybackControlSupportHelper(context, this) {
@Override
public int getUpdatePeriod() {
int totalTime = getControlsRow().getTotalTime();
@@ -105,6 +133,15 @@
}
};
+ mGlue.setInitialized(false);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mGlue.setInitialized(true);
+ }
+ }, MEDIA_PREPARATION_DELAY);
+ mGlue.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
mPlaybackControlsRowPresenter = mGlue.createControlsRowAndPresenter();
mPlaybackControlsRowPresenter.setSecondaryActionsHidden(SECONDARY_HIDDEN);
mListRowPresenter = new ListRowPresenter();
@@ -132,13 +169,14 @@
HeaderItem header = new HeaderItem(i, "Row " + i);
getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
}
+
}
@Override
public void onStart() {
super.onStart();
mGlue.setFadingEnabled(true);
- mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
+ mGlue.enableProgressUpdating(true);
((PlaybackOverlaySupportActivity) getActivity()).registerPictureInPictureListener(this);
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportActivity.java
new file mode 100644
index 0000000..069b37c
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportActivity.java
@@ -0,0 +1,71 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackActivity.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.support.v4.app.FragmentActivity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Host PlaybackFragment and provide PIP events.
+ */
+public class PlaybackSupportActivity extends FragmentActivity {
+ private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.playback_activity_support);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ for (PictureInPictureListener listener : mListeners) {
+ listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+
+ /**
+ * Register a PIP listener.
+ */
+ public void registerPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Unregister a PIP listener.
+ */
+ public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Interface of PIP event on Activity.
+ */
+ public interface PictureInPictureListener {
+ /**
+ * Called when Activity's PIP mode is changed.
+ */
+ void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+ }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportFragment.java
new file mode 100644
index 0000000..6d2fa51
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportFragment.java
@@ -0,0 +1,155 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+
+/**
+ * Example of PlaybackSupportFragment working with a PlaybackControlGlue.
+ */
+public class PlaybackSupportFragment
+ extends android.support.v17.leanback.app.PlaybackSupportFragment
+ implements PlaybackSupportActivity.PictureInPictureListener {
+ private static final String TAG = "leanback.PlaybackControlsFragment";
+
+ /**
+ * Change this to choose a different overlay background.
+ */
+ private static final int BACKGROUND_TYPE = PlaybackSupportFragment.BG_LIGHT;
+
+ /**
+ * Change the number of related content rows.
+ */
+ private static final int RELATED_CONTENT_ROWS = 3;
+
+ /**
+ * Change this to select hidden
+ */
+ private static final boolean SECONDARY_HIDDEN = false;
+
+ private static final int ROW_CONTROLS = 0;
+
+ private PlaybackControlGlue mGlue;
+ private ListRowPresenter mListRowPresenter;
+
+ public SparseArrayObjectAdapter getAdapter() {
+ return (SparseArrayObjectAdapter) super.getAdapter();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ setBackgroundType(BACKGROUND_TYPE);
+
+ createComponents(getActivity());
+ }
+
+ private void createComponents(Context context) {
+ mGlue = new PlaybackControlGlue(context) {
+ @Override
+ public int getUpdatePeriod() {
+ int totalTime = getControlsRow().getTotalTime();
+ if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+ return 1000;
+ }
+ return Math.max(16, totalTime / getView().getWidth());
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == R.id.lb_control_picture_in_picture) {
+ getActivity().enterPictureInPictureMode();
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ protected void onCreateControlsRowAndPresenter() {
+ super.onCreateControlsRowAndPresenter();
+ getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+ }
+ };
+
+ mGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
+ mListRowPresenter = new ListRowPresenter();
+
+ setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+ @Override
+ public Presenter getPresenter(Object object) {
+ if (object instanceof PlaybackControlsRow) {
+ return mGlue.getControlsRowPresenter();
+ } else if (object instanceof ListRow) {
+ return mListRowPresenter;
+ }
+ throw new IllegalArgumentException("Unhandled object: " + object);
+ }
+ }));
+
+ // Add the controls row
+ getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+ // Add related content rows
+ for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+ listRowAdapter.add("Some related content");
+ listRowAdapter.add("Other related content");
+ HeaderItem header = new HeaderItem(i, "Row " + i);
+ getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ ((PlaybackSupportActivity) getActivity()).registerPictureInPictureListener(this);
+ }
+
+ @Override
+ public void onStop() {
+ ((PlaybackSupportActivity) getActivity()).unregisterPictureInPictureListener(this);
+ super.onStop();
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ if (isInPictureInPictureMode) {
+ // Hide the controls in picture-in-picture mode.
+ setFadingEnabled(true);
+ fadeOut();
+ } else {
+ setFadingEnabled(mGlue.isMediaPlaying());
+ }
+ }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java
index af95920..f9dd055 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java
@@ -16,9 +16,9 @@
package com.example.android.leanback;
import android.os.Bundle;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
import android.support.v17.leanback.app.VideoFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
/**
* Fragment demonstrating the use of {@link android.support.v17.leanback.app.VideoFragment} to
@@ -30,7 +30,7 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
mMediaPlayerGlue.setHost(new VideoFragmentGlueHost(this));
mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java
index 3ffe161..0d55c89 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java
@@ -15,9 +15,9 @@
import android.net.Uri;
import android.os.Bundle;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
import android.support.v17.leanback.app.VideoFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
/**
* Fragment demonstrating the use of {@link android.support.v17.leanback.app.VideoFragment} to
@@ -44,7 +44,7 @@
getView().postDelayed(new Runnable() {
@Override
public void run() {
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
@Override
@@ -64,7 +64,7 @@
getView().postDelayed(new Runnable() {
@Override
public void run() {
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
@Override
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
index dde7bd2..3c2c1ac 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
@@ -18,9 +18,9 @@
import android.net.Uri;
import android.os.Bundle;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
import android.support.v17.leanback.app.VideoSupportFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
/**
* Fragment demonstrating the use of {@link android.support.v17.leanback.app.VideoSupportFragment} to
@@ -47,7 +47,7 @@
getView().postDelayed(new Runnable() {
@Override
public void run() {
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
@Override
@@ -67,7 +67,7 @@
getView().postDelayed(new Runnable() {
@Override
public void run() {
- mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+ mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index d4d8390..fcb3fa9 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -18,6 +18,7 @@
import android.os.Build;
import android.os.Bundle;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -190,6 +191,7 @@
getChildFragmentManager().beginTransaction()
.replace(R.id.details_rows_dock, mRowsFragment).commit();
}
+ installTitleView(inflater, mRootView, savedInstanceState);
mRowsFragment.setAdapter(mAdapter);
mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
@@ -328,7 +330,7 @@
* Creates an instance of {@link VideoFragment}. Subclasses can override this method
* and provide their own instance of a {@link Fragment}. When you provide your own instance of
* video fragment, you MUST also provide a custom
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}.
+ * {@link android.support.v17.leanback.media.PlaybackGlueHost}.
* @hide
*/
public Fragment onCreateVideoFragment() {
@@ -337,11 +339,11 @@
/**
* Creates an instance of
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}. The implementation
+ * {@link android.support.v17.leanback.media.PlaybackGlueHost}. The implementation
* of this host depends on the instance of video fragment {@link #onCreateVideoFragment()}.
* @hide
*/
- public PlaybackGlue.PlaybackGlueHost onCreateVideoFragmentHost(Fragment fragment) {
+ public PlaybackGlueHost onCreateVideoFragmentHost(Fragment fragment) {
return new VideoFragmentGlueHost((VideoFragment) fragment);
}
@@ -366,19 +368,22 @@
/**
* This method initializes a video fragment, create an instance of
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} using that fragment
+ * {@link android.support.v17.leanback.media.PlaybackGlueHost} using that fragment
* and return it.
* @hide
*/
- public final PlaybackGlue.PlaybackGlueHost createPlaybackGlueHost() {
+ public final PlaybackGlueHost createPlaybackGlueHost() {
Fragment fragment = findOrCreateVideoFragment();
return onCreateVideoFragmentHost(fragment);
}
void onRowSelected(int selectedPosition, int selectedSubPosition) {
ObjectAdapter adapter = getAdapter();
- if (adapter == null || adapter.size() == 0
- || (selectedPosition == 0 && selectedSubPosition == 0)) {
+ if (( mRowsFragment != null && mRowsFragment.getView() != null
+ && mRowsFragment.getView().hasFocus())
+ && (adapter == null || adapter.size() == 0
+ || (getVerticalGridView().getSelectedPosition() == 0
+ && getVerticalGridView().getSelectedSubPosition() == 0))) {
showTitle(true);
} else {
showTitle(false);
@@ -547,20 +552,31 @@
mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
@Override
public View onFocusSearch(View focused, int direction) {
- if (mVideoFragment == null) {
- return null;
- }
if (mRowsFragment.getVerticalGridView() != null
&& mRowsFragment.getVerticalGridView().hasFocus()) {
if (direction == View.FOCUS_UP) {
- slideOutGridView();
- return mVideoFragment.getView();
+ if (mVideoFragment != null && mVideoFragment.getView() != null) {
+ slideOutGridView();
+ showTitle(false);
+ return mVideoFragment.getView();
+ } else if (getTitleView() != null) {
+ return getTitleView();
+ }
}
- } else if (mVideoFragment.getView() != null
+ } else if (mVideoFragment != null && mVideoFragment.getView() != null
&& mVideoFragment.getView().hasFocus()) {
if (direction == View.FOCUS_DOWN) {
- slideInGridView();
- return mRowsFragment.getVerticalGridView();
+ if (mRowsFragment.getVerticalGridView() != null) {
+ showTitle(true);
+ slideInGridView();
+ return mRowsFragment.getVerticalGridView();
+ }
+ }
+ } else if (getTitleView() != null && getTitleView().hasFocus()) {
+ if (direction == View.FOCUS_DOWN) {
+ if (mRowsFragment.getVerticalGridView() != null) {
+ return mRowsFragment.getVerticalGridView();
+ }
}
}
return focused;
@@ -578,6 +594,7 @@
if (mVideoFragment != null && mVideoFragment.getView() != null
&& mVideoFragment.getView().hasFocus()) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
+ showTitle(true);
slideInGridView();
getVerticalGridView().requestFocus();
return true;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java
index 7d58b13..415b850 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.media.PlaybackGlue;
import android.support.v17.leanback.widget.Parallax;
import android.support.v17.leanback.widget.ParallaxRecyclerViewSource;
import android.support.v17.leanback.widget.ParallaxTarget;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
index cbf6324..a9bbf28 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -21,6 +21,7 @@
import android.os.Build;
import android.os.Bundle;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -193,6 +194,7 @@
getChildFragmentManager().beginTransaction()
.replace(R.id.details_rows_dock, mRowsSupportFragment).commit();
}
+ installTitleView(inflater, mRootView, savedInstanceState);
mRowsSupportFragment.setAdapter(mAdapter);
mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
@@ -331,7 +333,7 @@
* Creates an instance of {@link VideoSupportFragment}. Subclasses can override this method
* and provide their own instance of a {@link Fragment}. When you provide your own instance of
* video fragment, you MUST also provide a custom
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}.
+ * {@link android.support.v17.leanback.media.PlaybackGlueHost}.
* @hide
*/
public Fragment onCreateVideoSupportFragment() {
@@ -340,11 +342,11 @@
/**
* Creates an instance of
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}. The implementation
+ * {@link android.support.v17.leanback.media.PlaybackGlueHost}. The implementation
* of this host depends on the instance of video fragment {@link #onCreateVideoSupportFragment()}.
* @hide
*/
- public PlaybackGlue.PlaybackGlueHost onCreateVideoSupportFragmentHost(Fragment fragment) {
+ public PlaybackGlueHost onCreateVideoSupportFragmentHost(Fragment fragment) {
return new VideoSupportFragmentGlueHost((VideoSupportFragment) fragment);
}
@@ -369,19 +371,22 @@
/**
* This method initializes a video fragment, create an instance of
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} using that fragment
+ * {@link android.support.v17.leanback.media.PlaybackGlueHost} using that fragment
* and return it.
* @hide
*/
- public final PlaybackGlue.PlaybackGlueHost createPlaybackGlueHost() {
+ public final PlaybackGlueHost createPlaybackGlueHost() {
Fragment fragment = findOrCreateVideoSupportFragment();
return onCreateVideoSupportFragmentHost(fragment);
}
void onRowSelected(int selectedPosition, int selectedSubPosition) {
ObjectAdapter adapter = getAdapter();
- if (adapter == null || adapter.size() == 0
- || (selectedPosition == 0 && selectedSubPosition == 0)) {
+ if (( mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
+ && mRowsSupportFragment.getView().hasFocus())
+ && (adapter == null || adapter.size() == 0
+ || (getVerticalGridView().getSelectedPosition() == 0
+ && getVerticalGridView().getSelectedSubPosition() == 0))) {
showTitle(true);
} else {
showTitle(false);
@@ -550,20 +555,31 @@
mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
@Override
public View onFocusSearch(View focused, int direction) {
- if (mVideoSupportFragment == null) {
- return null;
- }
if (mRowsSupportFragment.getVerticalGridView() != null
&& mRowsSupportFragment.getVerticalGridView().hasFocus()) {
if (direction == View.FOCUS_UP) {
- slideOutGridView();
- return mVideoSupportFragment.getView();
+ if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
+ slideOutGridView();
+ showTitle(false);
+ return mVideoSupportFragment.getView();
+ } else if (getTitleView() != null) {
+ return getTitleView();
+ }
}
- } else if (mVideoSupportFragment.getView() != null
+ } else if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
&& mVideoSupportFragment.getView().hasFocus()) {
if (direction == View.FOCUS_DOWN) {
- slideInGridView();
- return mRowsSupportFragment.getVerticalGridView();
+ if (mRowsSupportFragment.getVerticalGridView() != null) {
+ showTitle(true);
+ slideInGridView();
+ return mRowsSupportFragment.getVerticalGridView();
+ }
+ }
+ } else if (getTitleView() != null && getTitleView().hasFocus()) {
+ if (direction == View.FOCUS_DOWN) {
+ if (mRowsSupportFragment.getVerticalGridView() != null) {
+ return mRowsSupportFragment.getVerticalGridView();
+ }
}
}
return focused;
@@ -581,6 +597,7 @@
if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
&& mVideoSupportFragment.getView().hasFocus()) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
+ showTitle(true);
slideInGridView();
getVerticalGridView().requestFocus();
return true;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
index 5c75506..7949bfb 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
@@ -14,7 +14,9 @@
* A helper class for implementing a glue layer between a
* {@link PlaybackOverlayFragment} and a
* {@link android.support.v4.media.session.MediaControllerCompat}.
+ * @deprecated Use {@link android.support.v17.leanback.media.MediaControllerGlue}.
*/
+@Deprecated
public abstract class MediaControllerGlue extends PlaybackControlGlue {
static final String TAG = "MediaControllerGlue";
static final boolean DEBUG = false;
@@ -55,10 +57,7 @@
* @param context
* @param fragment
* @param seekSpeeds Array of seek speeds for fast forward and rewind.
- * @deprecated Use
- * {@link #MediaControllerGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
*/
- @Deprecated
public MediaControllerGlue(Context context,
PlaybackOverlayFragment fragment,
int[] seekSpeeds) {
@@ -78,10 +77,7 @@
* @param fragment
* @param fastForwardSpeeds Array of seek speeds for fast forward.
* @param rewindSpeeds Array of seek speeds for rewind.
- * @deprecated Use
- * {@link #MediaControllerGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
*/
- @Deprecated
public MediaControllerGlue(Context context,
PlaybackOverlayFragment fragment,
int[] fastForwardSpeeds,
@@ -90,21 +86,6 @@
}
/**
- * Constructor for the glue.
- *
- * @param context
- * @param host Optional; if using a {@link PlaybackGlue.PlaybackGlueHost}, pass it in.
- * @param fastForwardSpeeds Array of seek speeds for fast forward.
- * @param rewindSpeeds Array of seek speeds for rewind.
- */
- public MediaControllerGlue(Context context,
- PlaybackGlueHost host,
- int[] fastForwardSpeeds,
- int[] rewindSpeeds) {
- super(context, host, fastForwardSpeeds, rewindSpeeds);
- }
-
- /**
* Attaches to the given media controller.
*/
public void attachToMediaController(MediaControllerCompat mediaController) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
index 4892b69..190ce22 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -14,31 +14,25 @@
package android.support.v17.leanback.app;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.util.Log;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.View;
-import java.lang.ref.WeakReference;
-
/**
* A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow}
- * and {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} that implements a
+ * and {@link PlaybackGlueHost} that implements a
* recommended approach to handling standard playback control actions such as play/pause,
* fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
* is a glue layer in that manages the configuration of and interaction between the
@@ -51,8 +45,7 @@
* </p>
*
* <p>To use an instance of the glue layer, first construct an instance. Constructor parameters
- * inform the glue what speed levels are supported for fast forward/rewind. Providing a
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} is optional.
+ * inform the glue what speed levels are supported for fast forward/rewind.
* </p>
*
* <p>If you have your own controls row you must pass it to {@link #setControlsRow}.
@@ -73,7 +66,7 @@
* </p>
*
* <p>This helper implements a key event handler. If you pass a
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}, it will configure it's
+ * {@link PlaybackOverlayFragment}, it will configure it's
* fragment to intercept all key events. Otherwise, you should set the glue object as key event
* handler to the ViewHolder when bound by your row presenter; see
* {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
@@ -83,144 +76,13 @@
* to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
* {@link #getUpdatePeriod()} provides a recommended update period.
* </p>
- *
+ * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue}
*/
-public abstract class PlaybackControlGlue extends PlaybackGlue
- implements OnActionClickedListener, View.OnKeyListener {
- /**
- * The adapter key for the first custom control on the left side
- * of the predefined primary controls.
- */
- public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
+@Deprecated
+public abstract class PlaybackControlGlue extends
+ android.support.v17.leanback.media.PlaybackControlGlue {
- /**
- * The adapter key for the skip to previous control.
- */
- public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
-
- /**
- * The adapter key for the rewind control.
- */
- public static final int ACTION_REWIND = 0x20;
-
- /**
- * The adapter key for the play/pause control.
- */
- public static final int ACTION_PLAY_PAUSE = 0x40;
-
- /**
- * The adapter key for the fast forward control.
- */
- public static final int ACTION_FAST_FORWARD = 0x80;
-
- /**
- * The adapter key for the skip to next control.
- */
- public static final int ACTION_SKIP_TO_NEXT = 0x100;
-
- /**
- * The adapter key for the first custom control on the right side
- * of the predefined primary controls.
- */
- public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
-
- /**
- * Invalid playback speed.
- */
- public static final int PLAYBACK_SPEED_INVALID = -1;
-
- /**
- * Speed representing playback state that is paused.
- */
- public static final int PLAYBACK_SPEED_PAUSED = 0;
-
- /**
- * Speed representing playback state that is playing normally.
- */
- public static final int PLAYBACK_SPEED_NORMAL = 1;
-
- /**
- * The initial (level 0) fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L0 = 10;
-
- /**
- * The level 1 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L1 = 11;
-
- /**
- * The level 2 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L2 = 12;
-
- /**
- * The level 3 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L3 = 13;
-
- /**
- * The level 4 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L4 = 14;
-
- static final String TAG = "PlaybackControlGlue";
- static final boolean DEBUG = false;
-
- static final int MSG_UPDATE_PLAYBACK_STATE = 100;
- private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
- private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
- - PLAYBACK_SPEED_FAST_L0 + 1;
-
- private final int[] mFastForwardSpeeds;
- private final int[] mRewindSpeeds;
- private PlaybackControlsRow mControlsRow;
- private SparseArrayObjectAdapter mPrimaryActionsAdapter;
- private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
- private PlaybackControlsRow.SkipNextAction mSkipNextAction;
- private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
- private PlaybackControlsRow.FastForwardAction mFastForwardAction;
- private PlaybackControlsRow.RewindAction mRewindAction;
OnItemViewClickedListener mExternalOnItemViewClickedListener;
- private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
- private boolean mFadeWhenPlaying = true;
-
- static class UpdatePlaybackStateHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
- PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
- if (glue != null) {
- glue.updatePlaybackState();
- }
- }
- }
- }
-
- static final Handler sHandler = new UpdatePlaybackStateHandler();
-
- final WeakReference<PlaybackControlGlue> mGlueWeakReference = new WeakReference(this);
-
- /**
- * Interface allowing the application to handle input events.
- * @deprecated Use
- * {@link PlaybackGlue.PlaybackGlueHost#setOnKeyInterceptListener(View.OnKeyListener)}.
- */
- @Deprecated
- public interface InputEventHandler {
- /**
- * Called when an {@link InputEvent} is received.
- *
- * @return If the event should be consumed, return true. To allow the event to
- * continue on to the next handler, return false.
- */
- boolean handleInputEvent(InputEvent event);
- }
/**
* Constructor for the glue.
@@ -229,7 +91,7 @@
* @param seekSpeeds Array of seek speeds for fast forward and rewind.
*/
public PlaybackControlGlue(Context context, int[] seekSpeeds) {
- this(context, (PlaybackGlueHost) null, seekSpeeds, seekSpeeds);
+ super(context, seekSpeeds, seekSpeeds);
}
/**
@@ -242,7 +104,7 @@
public PlaybackControlGlue(Context context,
int[] fastForwardSpeeds,
int[] rewindSpeeds) {
- this(context, (PlaybackGlueHost) null, fastForwardSpeeds, rewindSpeeds);
+ super(context, fastForwardSpeeds, rewindSpeeds);
}
/**
@@ -251,10 +113,7 @@
* @param context
* @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
* @param seekSpeeds Array of seek speeds for fast forward and rewind.
- * @deprecated Use
- * {@link #PlaybackControlGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
*/
- @Deprecated
public PlaybackControlGlue(Context context,
PlaybackOverlayFragment fragment,
int[] seekSpeeds) {
@@ -268,110 +127,26 @@
* @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
* @param fastForwardSpeeds Array of seek speeds for fast forward.
* @param rewindSpeeds Array of seek speeds for rewind.
- * @deprecated Use
- * {@link #PlaybackControlGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
*/
- @Deprecated
public PlaybackControlGlue(Context context,
PlaybackOverlayFragment fragment,
int[] fastForwardSpeeds,
int[] rewindSpeeds) {
- this(context, fragment == null ? (PlaybackGlueHost) null:
- new PlaybackGlueHostOld(fragment), fastForwardSpeeds, rewindSpeeds);
- }
-
- /**
- * Constructor for the glue.
- *
- * @param context
- * @param host Optional; if using a {@link PlaybackGlue.PlaybackGlueHost}, pass it in.
- * @param fastForwardSpeeds Array of seek speeds for fast forward.
- * @param rewindSpeeds Array of seek speeds for rewind.
- */
- public PlaybackControlGlue(Context context,
- PlaybackGlueHost host,
- int[] fastForwardSpeeds,
- int[] rewindSpeeds) {
- super(context);
- if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
- throw new IllegalStateException("invalid fastForwardSpeeds array size");
- }
- mFastForwardSpeeds = fastForwardSpeeds;
- if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
- throw new IllegalStateException("invalid rewindSpeeds array size");
- }
- mRewindSpeeds = rewindSpeeds;
- setHost(host);
+ super(context, fastForwardSpeeds, rewindSpeeds);
+ setHost(fragment == null ? (PlaybackGlueHost) null : new PlaybackGlueHostOld(fragment));
}
@Override
- public void setHost(PlaybackGlueHost host) {
- super.setHost(host);
- if (getHost() != null) {
- if (getHost() instanceof PlaybackGlueHostOld) {
- ((PlaybackGlueHostOld) getHost()).mGlue = this;
- }
- getHost().setOnKeyInterceptListener(this);
- getHost().setOnActionClickedListener(this);
- getHost().setPlaybackRowPresenter(createControlsRowAndPresenter());
- getHost().setPlaybackRow(getControlsRow());
- getHost().setHostLifeCycleCallback(new HostLifecycleCallback() {
- @Override
- public void onHostStart() {
- enableProgressUpdating(true);
- }
-
- @Override
- public void onHostStop() {
- enableProgressUpdating(false);
- }
- });
+ protected void onAttachedToHost(PlaybackGlueHost host) {
+ super.onAttachedToHost(host);
+ if (host instanceof PlaybackGlueHostOld) {
+ ((PlaybackGlueHostOld) host).mGlue = this;
}
}
/**
- * Helper method for instantiating a {@link PlaybackControlsRow} and corresponding
- * {@link PlaybackControlsRowPresenter}.
- */
- public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
- PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
- setControlsRow(controlsRow);
-
- final AbstractDetailsDescriptionPresenter detailsPresenter =
- new AbstractDetailsDescriptionPresenter() {
- @Override
- protected void onBindDescription(AbstractDetailsDescriptionPresenter.ViewHolder
- viewHolder, Object object) {
- PlaybackControlGlue glue = (PlaybackControlGlue) object;
- if (glue.hasValidMedia()) {
- viewHolder.getTitle().setText(glue.getMediaTitle());
- viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
- } else {
- viewHolder.getTitle().setText("");
- viewHolder.getSubtitle().setText("");
- }
- }
- };
-
- return new PlaybackControlsRowPresenter(detailsPresenter) {
- @Override
- protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
- super.onBindRowViewHolder(vh, item);
- vh.setOnKeyListener(PlaybackControlGlue.this);
- }
- @Override
- protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
- super.onUnbindRowViewHolder(vh);
- vh.setOnKeyListener(null);
- }
- };
- }
-
- /**
* Returns the fragment.
- * @deprecated The glue is no longer associated with a fragment, use {@link #getHost()}.
*/
- @Deprecated
public PlaybackOverlayFragment getFragment() {
if (getHost() instanceof PlaybackGlueHostOld) {
return ((PlaybackGlueHostOld)getHost()).mFragment;
@@ -380,456 +155,6 @@
}
/**
- * Returns the fast forward speeds.
- */
- public int[] getFastForwardSpeeds() {
- return mFastForwardSpeeds;
- }
-
- /**
- * Returns the rewind speeds.
- */
- public int[] getRewindSpeeds() {
- return mRewindSpeeds;
- }
-
- /**
- * Sets the controls to fade after a timeout when media is playing.
- */
- public void setFadingEnabled(boolean enable) {
- mFadeWhenPlaying = enable;
- if (!mFadeWhenPlaying && getHost() != null) {
- getHost().setFadingEnabled(false);
- }
- }
-
- /**
- * Returns true if controls are set to fade when media is playing.
- */
- public boolean isFadingEnabled() {
- return mFadeWhenPlaying;
- }
-
- /**
- * Set the {@link OnItemViewClickedListener} to be called if the click event
- * is not handled internally.
- * @param listener
- * @deprecated Don't call this. Instead use the listener on the fragment yourself.
- */
- @Deprecated
- public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
- mExternalOnItemViewClickedListener = listener;
- }
-
- /**
- * Returns the {@link OnItemViewClickedListener}.
- * @deprecated Don't call this. Instead use the listener on the fragment yourself.
- */
- @Deprecated
- public OnItemViewClickedListener getOnItemViewClickedListener() {
- return mExternalOnItemViewClickedListener;
- }
-
- /**
- * Sets the controls row to be managed by the glue layer.
- * The primary actions and playback state related aspects of the row
- * are updated by the glue.
- */
- public void setControlsRow(PlaybackControlsRow controlsRow) {
- mControlsRow = controlsRow;
- mPrimaryActionsAdapter = createPrimaryActionsAdapter(
- new ControlButtonPresenterSelector());
- mControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
- updateControlsRow();
- }
-
- /**
- * Returns the playback controls row managed by the glue layer.
- */
- public PlaybackControlsRow getControlsRow() {
- return mControlsRow;
- }
-
- /**
- * Override this to start/stop a runnable to call {@link #updateProgress} at
- * an interval such as {@link #getUpdatePeriod}.
- */
- public void enableProgressUpdating(boolean enable) {
- }
-
- /**
- * Returns the time period in milliseconds that should be used
- * to update the progress. See {@link #updateProgress()}.
- */
- public int getUpdatePeriod() {
- // TODO: calculate a better update period based on total duration and screen size
- return 500;
- }
-
- /**
- * Updates the progress bar based on the current media playback position.
- */
- public void updateProgress() {
- int position = getCurrentPosition();
- if (DEBUG) Log.v(TAG, "updateProgress " + position);
- mControlsRow.setCurrentTime(position);
- }
-
- /**
- * Handles action clicks. A subclass may override this add support for additional actions.
- */
- @Override
- public void onActionClicked(Action action) {
- dispatchAction(action, null);
- }
-
- /**
- * Handles key events and returns true if handled. A subclass may override this to provide
- * additional support.
- */
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_BACK:
- case KeyEvent.KEYCODE_ESCAPE:
- boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
- || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
- if (abortSeek) {
- mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
- startPlayback(mPlaybackSpeed);
- updatePlaybackStatusAfterUserAction();
- return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
- }
- return false;
- }
- Action action = mControlsRow.getActionForKeyCode(mPrimaryActionsAdapter, keyCode);
-
- if (action != null) {
- if (action == mPrimaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
- || action == mPrimaryActionsAdapter.lookup(ACTION_REWIND)
- || action == mPrimaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
- || action == mPrimaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
- || action == mPrimaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- dispatchAction(action, event);
- }
- return true;
- }
- }
- return false;
- }
-
- /**
- * Called when the given action is invoked, either by click or keyevent.
- */
- boolean dispatchAction(Action action, KeyEvent keyEvent) {
- boolean handled = false;
- if (action == mPlayPauseAction) {
- boolean canPlay = keyEvent == null
- || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
- || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
- boolean canPause = keyEvent == null
- || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
- || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
- // PLAY_PAUSE PLAY PAUSE
- // playing paused paused
- // paused playing playing
- // ff/rw playing playing paused
- if (canPause &&
- (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
- mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
- mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
- pausePlayback();
- } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
- mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
- startPlayback(mPlaybackSpeed);
- }
- updatePlaybackStatusAfterUserAction();
- handled = true;
- } else if (action == mSkipNextAction) {
- skipToNext();
- handled = true;
- } else if (action == mSkipPreviousAction) {
- skipToPrevious();
- handled = true;
- } else if (action == mFastForwardAction) {
- if (mPlaybackSpeed < getMaxForwardSpeedId()) {
- switch (mPlaybackSpeed) {
- case PLAYBACK_SPEED_FAST_L0:
- case PLAYBACK_SPEED_FAST_L1:
- case PLAYBACK_SPEED_FAST_L2:
- case PLAYBACK_SPEED_FAST_L3:
- mPlaybackSpeed++;
- break;
- default:
- mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
- break;
- }
- startPlayback(mPlaybackSpeed);
- updatePlaybackStatusAfterUserAction();
- }
- handled = true;
- } else if (action == mRewindAction) {
- if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
- switch (mPlaybackSpeed) {
- case -PLAYBACK_SPEED_FAST_L0:
- case -PLAYBACK_SPEED_FAST_L1:
- case -PLAYBACK_SPEED_FAST_L2:
- case -PLAYBACK_SPEED_FAST_L3:
- mPlaybackSpeed--;
- break;
- default:
- mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
- break;
- }
- startPlayback(mPlaybackSpeed);
- updatePlaybackStatusAfterUserAction();
- }
- handled = true;
- }
- return handled;
- }
-
- private int getMaxForwardSpeedId() {
- return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
- }
-
- private int getMaxRewindSpeedId() {
- return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
- }
-
- private void updateControlsRow() {
- updateRowMetadata();
- sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
- updatePlaybackState();
- }
-
- private void updatePlaybackStatusAfterUserAction() {
- updatePlaybackState(mPlaybackSpeed);
- // Sync playback state after a delay
- sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
- sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
- mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
- }
-
- private void updateRowMetadata() {
- if (mControlsRow == null) {
- return;
- }
-
- if (DEBUG) Log.v(TAG, "updateRowMetadata");
-
- if (!hasValidMedia()) {
- mControlsRow.setImageDrawable(null);
- mControlsRow.setTotalTime(0);
- mControlsRow.setCurrentTime(0);
- } else {
- mControlsRow.setImageDrawable(getMediaArt());
- mControlsRow.setTotalTime(getMediaDuration());
- mControlsRow.setCurrentTime(getCurrentPosition());
- }
-
- if (getHost() != null) {
- getHost().notifyPlaybackRowChanged();
- }
- }
-
- void updatePlaybackState() {
- if (hasValidMedia()) {
- mPlaybackSpeed = getCurrentSpeedId();
- updatePlaybackState(mPlaybackSpeed);
- }
- }
-
- private void updatePlaybackState(int playbackSpeed) {
- if (mControlsRow == null) {
- return;
- }
-
- final long actions = getSupportedActions();
- if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
- if (mSkipPreviousAction == null) {
- mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
- }
- mPrimaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
- } else {
- mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
- mSkipPreviousAction = null;
- }
- if ((actions & ACTION_REWIND) != 0) {
- if (mRewindAction == null) {
- mRewindAction = new PlaybackControlsRow.RewindAction(
- getContext(),
- mRewindSpeeds.length);
- }
- mPrimaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
- } else {
- mPrimaryActionsAdapter.clear(ACTION_REWIND);
- mRewindAction = null;
- }
- if ((actions & ACTION_PLAY_PAUSE) != 0) {
- if (mPlayPauseAction == null) {
- mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
- }
- mPrimaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
- } else {
- mPrimaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
- mPlayPauseAction = null;
- }
- if ((actions & ACTION_FAST_FORWARD) != 0) {
- if (mFastForwardAction == null) {
- mFastForwardAction = new PlaybackControlsRow.FastForwardAction(
- getContext(),
- mFastForwardSpeeds.length);
- }
- mPrimaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
- } else {
- mPrimaryActionsAdapter.clear(ACTION_FAST_FORWARD);
- mFastForwardAction = null;
- }
- if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
- if (mSkipNextAction == null) {
- mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
- }
- mPrimaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
- } else {
- mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
- mSkipNextAction = null;
- }
-
- if (mFastForwardAction != null) {
- int index = 0;
- if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
- index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
- }
- if (mFastForwardAction.getIndex() != index) {
- mFastForwardAction.setIndex(index);
- notifyItemChanged(mPrimaryActionsAdapter, mFastForwardAction);
- }
- }
- if (mRewindAction != null) {
- int index = 0;
- if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
- index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
- }
- if (mRewindAction.getIndex() != index) {
- mRewindAction.setIndex(index);
- notifyItemChanged(mPrimaryActionsAdapter, mRewindAction);
- }
- }
-
- if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
- updateProgress();
- enableProgressUpdating(false);
- } else {
- enableProgressUpdating(true);
- }
-
- if (mFadeWhenPlaying && getHost() != null) {
- getHost().setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
- }
-
- if (mPlayPauseAction != null) {
- int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
- ? PlaybackControlsRow.PlayPauseAction.PLAY
- : PlaybackControlsRow.PlayPauseAction.PAUSE;
- if (mPlayPauseAction.getIndex() != index) {
- mPlayPauseAction.setIndex(index);
- notifyItemChanged(mPrimaryActionsAdapter, mPlayPauseAction);
- }
- }
- }
-
- private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
- int index = adapter.indexOf(object);
- if (index >= 0) {
- adapter.notifyArrayItemRangeChanged(index, 1);
- }
- }
-
- private static String getSpeedString(int speed) {
- switch (speed) {
- case PLAYBACK_SPEED_INVALID:
- return "PLAYBACK_SPEED_INVALID";
- case PLAYBACK_SPEED_PAUSED:
- return "PLAYBACK_SPEED_PAUSED";
- case PLAYBACK_SPEED_NORMAL:
- return "PLAYBACK_SPEED_NORMAL";
- case PLAYBACK_SPEED_FAST_L0:
- return "PLAYBACK_SPEED_FAST_L0";
- case PLAYBACK_SPEED_FAST_L1:
- return "PLAYBACK_SPEED_FAST_L1";
- case PLAYBACK_SPEED_FAST_L2:
- return "PLAYBACK_SPEED_FAST_L2";
- case PLAYBACK_SPEED_FAST_L3:
- return "PLAYBACK_SPEED_FAST_L3";
- case PLAYBACK_SPEED_FAST_L4:
- return "PLAYBACK_SPEED_FAST_L4";
- case -PLAYBACK_SPEED_FAST_L0:
- return "-PLAYBACK_SPEED_FAST_L0";
- case -PLAYBACK_SPEED_FAST_L1:
- return "-PLAYBACK_SPEED_FAST_L1";
- case -PLAYBACK_SPEED_FAST_L2:
- return "-PLAYBACK_SPEED_FAST_L2";
- case -PLAYBACK_SPEED_FAST_L3:
- return "-PLAYBACK_SPEED_FAST_L3";
- case -PLAYBACK_SPEED_FAST_L4:
- return "-PLAYBACK_SPEED_FAST_L4";
- }
- return null;
- }
-
- /**
- * Returns true if there is a valid media item.
- */
- public abstract boolean hasValidMedia();
-
- /**
- * Returns true if media is currently playing.
- */
- public abstract boolean isMediaPlaying();
-
- /**
- * Returns the title of the media item.
- */
- public abstract CharSequence getMediaTitle();
-
- /**
- * Returns the subtitle of the media item.
- */
- public abstract CharSequence getMediaSubtitle();
-
- /**
- * Returns the duration of the media item in milliseconds.
- */
- public abstract int getMediaDuration();
-
- /**
- * Returns a bitmap of the art for the media item.
- */
- public abstract Drawable getMediaArt();
-
- /**
- * Returns a bitmask of actions supported by the media player.
- */
- public abstract long getSupportedActions();
-
- /**
- * Returns the current playback speed. When playing normally,
- * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
- */
- public abstract int getCurrentSpeedId();
-
- /**
- * Returns the current position of the media item in milliseconds.
- */
- public abstract int getCurrentPosition();
-
- /**
* Start playback at the given speed.
* @deprecated use {@link #play()} instead.
*
@@ -861,68 +186,112 @@
@Deprecated
protected void skipToPrevious() {}
+ @Override
+ public final void next() {
+ skipToNext();
+ }
+
+ @Override
+ public final void previous() {
+ skipToPrevious();
+ }
+
+ @Override
+ public final void play(int speed) {
+ startPlayback(speed);
+ }
+
+ @Override
+ public final void pause() {
+ pausePlayback();
+ }
+
/**
* This method invoked when the playback controls row has changed. The adapter
- * containing this row should be notified. This method would be delegated to
- * {@see android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost#notifyPlaybackRowChanged}.
- * @deprecated see {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}.
+ * containing this row should be notified.
+ */
+ protected void onRowChanged(PlaybackControlsRow row) {
+ }
+
+ /**
+ * Set the {@link OnItemViewClickedListener} to be called if the click event
+ * is not handled internally.
+ * @param listener
+ * @deprecated Don't call this. Instead use the listener on the fragment yourself.
*/
@Deprecated
- protected void onRowChanged(PlaybackControlsRow row) {
- if (getHost() != null) {
- getHost().notifyPlaybackRowChanged();
- }
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ mExternalOnItemViewClickedListener = listener;
}
/**
- * Creates the primary action adapter. May be overridden to add additional primary
- * actions to the adapter.
+ * Returns the {@link OnItemViewClickedListener}.
+ * @deprecated Don't call this. Instead use the listener on the fragment yourself.
*/
+ @Deprecated
+ public OnItemViewClickedListener getOnItemViewClickedListener() {
+ return mExternalOnItemViewClickedListener;
+ }
+
+ @Override
+ protected void onCreateControlsRowAndPresenter() {
+ // backward compatible, we dont create row / presenter by default.
+ // User is expected to call createControlsRowAndPresenter() or setControlsRow()
+ // explicitly.
+ }
+
+ /**
+ * Helper method for instantiating a
+ * {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding
+ * {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.
+ */
+ public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+ super.onCreateControlsRowAndPresenter();
+ return getControlsRowPresenter();
+ }
+
protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
PresenterSelector presenterSelector) {
- return new SparseArrayObjectAdapter(presenterSelector);
+ return super.createPrimaryActionsAdapter(presenterSelector);
}
/**
- * Must be called appropriately by a subclass when the playback state has changed.
- * It updates the playback state displayed on the media player.
+ * Interface allowing the application to handle input events.
+ * @deprecated Use
+ * {@link PlaybackGlueHost#setOnKeyInterceptListener(View.OnKeyListener)}.
*/
- protected void onStateChanged() {
- if (DEBUG) Log.v(TAG, "onStateChanged");
- // If a pending control button update is present, delay
- // the update until the state settles.
- if (!hasValidMedia()) {
- return;
- }
- if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
- sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
- if (getCurrentSpeedId() != mPlaybackSpeed) {
- if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
- sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
- mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
- } else {
- if (DEBUG) Log.v(TAG, "Update state matches expectation");
- updatePlaybackState();
- }
- } else {
- updatePlaybackState();
- }
- }
-
- /**
- * Must be called appropriately by a subclass when the metadata state has changed.
- */
- protected void onMetadataChanged() {
- if (DEBUG) Log.v(TAG, "onMetadataChanged");
- updateRowMetadata();
+ @Deprecated
+ public interface InputEventHandler {
+ /**
+ * Called when an {@link InputEvent} is received.
+ *
+ * @return If the event should be consumed, return true. To allow the event to
+ * continue on to the next handler, return false.
+ */
+ boolean handleInputEvent(InputEvent event);
}
static final class PlaybackGlueHostOld extends PlaybackGlueHost {
final PlaybackOverlayFragment mFragment;
PlaybackControlGlue mGlue;
+ OnActionClickedListener mActionClickedListener;
public PlaybackGlueHostOld(PlaybackOverlayFragment fragment) {
mFragment = fragment;
+ mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ if (item instanceof Action
+ && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
+ && mActionClickedListener != null) {
+ mActionClickedListener.onActionClicked((Action) item);
+ } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
+ mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
+ item, rowViewHolder, row);
+ }
+ }
+ });
}
@Override
@@ -931,13 +300,13 @@
}
@Override
- public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListenerr) {
+ public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListener) {
mFragment.setEventHandler( new InputEventHandler() {
@Override
public boolean handleInputEvent(InputEvent event) {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
- return onKeyListenerr.onKey(null, keyEvent.getKeyCode(), keyEvent);
+ return onKeyListener.onKey(null, keyEvent.getKeyCode(), keyEvent);
}
return false;
}
@@ -946,19 +315,22 @@
@Override
public void setOnActionClickedListener(final OnActionClickedListener listener) {
- mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Action) {
- listener.onActionClicked((Action)item);
- if (mGlue.mExternalOnItemViewClickedListener != null) {
- mGlue.mExternalOnItemViewClickedListener.onItemClicked(itemViewHolder,
- item, rowViewHolder, row);
- }
- }
- }
- });
+ mActionClickedListener = listener;
+ }
+
+ @Override
+ public void setHostCallback(HostCallback callback) {
+ mFragment.setHostCallback(callback);
+ }
+
+ @Override
+ public void fadeOut() {
+ mFragment.fadeOut();
+ }
+
+ @Override
+ public void notifyPlaybackRowChanged() {
+ mGlue.onRowChanged(mGlue.getControlsRow());
}
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
index 5beb87f..b3d19ae 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
@@ -3,9 +3,11 @@
package android.support.v17.leanback.app;
import android.content.Context;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
@@ -14,8 +16,8 @@
import android.view.View;
/**
- * @deprecated Use {@link PlaybackControlGlue} and {@link PlaybackSupportFragmentGlueHost} for
- * {@link PlaybackSupportFragment}.
+ * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue} and
+ * {@link PlaybackSupportFragmentGlueHost} for {@link PlaybackSupportFragment}.
*/
@Deprecated
public abstract class PlaybackControlSupportGlue extends PlaybackControlGlue {
@@ -103,22 +105,19 @@
public static final int PLAYBACK_SPEED_FAST_L4 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L4;
public PlaybackControlSupportGlue(Context context, int[] seekSpeeds) {
- super(context, seekSpeeds);
+ this(context, null, seekSpeeds, seekSpeeds);
}
public PlaybackControlSupportGlue(
Context context, int[] fastForwardSpeeds, int[] rewindSpeeds) {
- super(context, fastForwardSpeeds, rewindSpeeds);
+ this(context, null, fastForwardSpeeds, rewindSpeeds);
}
public PlaybackControlSupportGlue(
Context context,
PlaybackOverlaySupportFragment fragment,
int[] seekSpeeds) {
- super(context,
- fragment == null ? null: new PlaybackSupportGlueHostOld(fragment),
- seekSpeeds,
- seekSpeeds);
+ this(context, fragment, seekSpeeds, seekSpeeds);
}
public PlaybackControlSupportGlue(
@@ -126,15 +125,13 @@
PlaybackOverlaySupportFragment fragment,
int[] fastForwardSpeeds,
int[] rewindSpeeds) {
- super(context,
- fragment == null ? null: new PlaybackSupportGlueHostOld(fragment),
- fastForwardSpeeds,
- rewindSpeeds);
+ super(context, fastForwardSpeeds, rewindSpeeds);
+ setHost(fragment == null ? null : new PlaybackSupportGlueHostOld(fragment));
}
@Override
- public void setHost(PlaybackGlueHost host) {
- super.setHost(host);
+ protected void onAttachedToHost(PlaybackGlueHost host) {
+ super.onAttachedToHost(host);
if (host instanceof PlaybackSupportGlueHostOld) {
((PlaybackSupportGlueHostOld) host).mGlue = this;
}
@@ -142,10 +139,25 @@
static final class PlaybackSupportGlueHostOld extends PlaybackGlueHost {
final PlaybackOverlaySupportFragment mFragment;
- PlaybackControlGlue mGlue;
+ PlaybackControlSupportGlue mGlue;
+ OnActionClickedListener mActionClickedListener;
public PlaybackSupportGlueHostOld(PlaybackOverlaySupportFragment fragment) {
mFragment = fragment;
+ mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ if (item instanceof Action
+ && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
+ && mActionClickedListener != null) {
+ mActionClickedListener.onActionClicked((Action) item);
+ } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
+ mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
+ item, rowViewHolder, row);
+ }
+ }
+ });
}
@Override
@@ -169,19 +181,22 @@
@Override
public void setOnActionClickedListener(final OnActionClickedListener listener) {
- mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Action) {
- listener.onActionClicked((Action)item);
- if (mGlue.mExternalOnItemViewClickedListener != null) {
- mGlue.mExternalOnItemViewClickedListener.onItemClicked(itemViewHolder,
- item, rowViewHolder, row);
- }
- }
- }
- });
+ mActionClickedListener = listener;
+ }
+
+ @Override
+ public void setHostCallback(HostCallback callback) {
+ mFragment.setHostCallback(callback);
+ }
+
+ @Override
+ public void fadeOut() {
+ mFragment.fadeOut();
+ }
+
+ @Override
+ public void notifyPlaybackRowChanged() {
+ mGlue.onRowChanged(mGlue.getControlsRow());
}
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
index b97930a..02a0257 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
@@ -28,8 +28,10 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
@@ -74,10 +76,11 @@
* A dark translucent background.
*/
public static final int BG_DARK = 1;
- private PlaybackGlue.HostLifecycleCallback mHostLifecycleCallback;
+ private PlaybackGlueHost.HostCallback mHostCallback;
/**
* Resets the focus on the button in the middle of control row.
+ * @hide
*/
public void resetFocus() {
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
@@ -109,25 +112,43 @@
private ObjectAdapter mAdapter;
private PlaybackRowPresenter mPresenter;
private Row mRow;
+ private BaseOnItemViewSelectedListener mExternalItemSelectedListener;
private BaseOnItemViewClickedListener mExternalItemClickedListener;
private BaseOnItemViewClickedListener mPlaybackItemClickedListener;
- private BaseOnItemViewClickedListener mOnItemViewClickedListener = new BaseOnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder,
- Object item,
- RowPresenter.ViewHolder rowViewHolder,
- Object row) {
- if (mPlaybackItemClickedListener != null
- && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
- mPlaybackItemClickedListener.onItemClicked(
- itemViewHolder, item, rowViewHolder, row);
- }
- if (mExternalItemClickedListener != null) {
- mExternalItemClickedListener.onItemClicked(
- itemViewHolder, item, rowViewHolder, row);
- }
- }
- };
+
+ private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
+ new BaseOnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder,
+ Object item,
+ RowPresenter.ViewHolder rowViewHolder,
+ Object row) {
+ if (mPlaybackItemClickedListener != null
+ && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
+ mPlaybackItemClickedListener.onItemClicked(
+ itemViewHolder, item, rowViewHolder, row);
+ }
+ if (mExternalItemClickedListener != null) {
+ mExternalItemClickedListener.onItemClicked(
+ itemViewHolder, item, rowViewHolder, row);
+ }
+ }
+ };
+
+ private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
+ new BaseOnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder,
+ Object item,
+ RowPresenter.ViewHolder rowViewHolder,
+ Object row) {
+ if (mExternalItemSelectedListener != null) {
+ mExternalItemSelectedListener.onItemSelected(
+ itemViewHolder, item, rowViewHolder, row);
+ }
+ }
+ };
+
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
public ObjectAdapter getAdapter() {
@@ -137,6 +158,7 @@
/**
* Listener allowing the application to receive notification of fade in and/or fade out
* completion events.
+ * @hide
*/
public static class OnFadeCompleteListener {
public void onFadeInComplete() {
@@ -295,6 +317,7 @@
/**
* Sets the listener to be called when fade in or out has completed.
+ * @hide
*/
public void setFadeCompleteListener(OnFadeCompleteListener listener) {
mFadeCompleteListener = listener;
@@ -302,6 +325,7 @@
/**
* Returns the listener to be called when fade in or out has completed.
+ * @hide
*/
public OnFadeCompleteListener getFadeCompleteListener() {
return mFadeCompleteListener;
@@ -315,13 +339,6 @@
}
/**
- * Returns the input event handler.
- */
- public final View.OnKeyListener getEventHandler() {
- return mInputEventHandler;
- }
-
- /**
* Tickles the playback controls. Fades in the view if it was faded out,
* otherwise resets the fade out timer. Tickling on input events is handled
* by the fragment.
@@ -409,6 +426,9 @@
}
getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+ if (mHostCallback != null) {
+ mHostCallback.onHostResume();
+ }
}
private void startFadeTimer() {
@@ -742,6 +762,7 @@
} else {
mRowsFragment.setAdapter(mAdapter);
}
+ mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
mBgAlpha = 255;
@@ -751,11 +772,11 @@
}
/**
- * Sets the {@link PlaybackGlue.HostLifecycleCallback}. Implementor of this interface will
+ * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
* take appropriate actions to take action when the hosting fragment starts/stops processing.
*/
- public void setHostLifecycleCallback(PlaybackGlue.HostLifecycleCallback hostLifecycleCallback) {
- this.mHostLifecycleCallback = hostLifecycleCallback;
+ public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+ this.mHostCallback = hostCallback;
}
@Override
@@ -763,17 +784,34 @@
super.onStart();
setupChildFragmentLayout();
mRowsFragment.setAdapter(mAdapter);
- if (mHostLifecycleCallback != null) {
- mHostLifecycleCallback.onHostStart();
+ if (mHostCallback != null) {
+ mHostCallback.onHostStart();
}
}
@Override
public void onStop() {
- super.onStop();
- if (mHostLifecycleCallback != null) {
- mHostLifecycleCallback.onHostStop();
+ if (mHostCallback != null) {
+ mHostCallback.onHostStop();
}
+ super.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ if (mHostCallback != null) {
+ mHostCallback.onHostPause();
+ }
+ super.onPause();
+ }
+
+ /**
+ * This listener is called every time there is a selection in {@link RowsFragment}. This can
+ * be used by users to take additional actions such as animations.
+ * @hide
+ */
+ public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
+ mExternalItemSelectedListener = listener;
}
/**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index fc384a5..fdaa6ef 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -13,6 +13,7 @@
*/
package android.support.v17.leanback.app;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
@@ -23,10 +24,10 @@
import android.view.View;
/**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
* the interaction between this class and {@link PlaybackFragment}.
*/
-public class PlaybackFragmentGlueHost extends PlaybackGlue.PlaybackGlueHost {
+public class PlaybackFragmentGlueHost extends PlaybackGlueHost {
private final PlaybackFragment mFragment;
public PlaybackFragmentGlueHost(PlaybackFragment fragment) {
@@ -45,20 +46,24 @@
@Override
public void setOnActionClickedListener(final OnActionClickedListener listener) {
- mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Action) {
- listener.onActionClicked((Action)item);
+ if (listener == null) {
+ mFragment.setOnPlaybackItemViewClickedListener(null);
+ } else {
+ mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ if (item instanceof Action) {
+ listener.onActionClicked((Action) item);
+ }
}
- }
- });
+ });
+ }
}
@Override
- public void setHostLifeCycleCallback(PlaybackGlue.HostLifecycleCallback callback) {
- mFragment.setHostLifecycleCallback(callback);
+ public void setHostCallback(HostCallback callback) {
+ mFragment.setHostCallback(callback);
}
@Override
@@ -75,4 +80,9 @@
public void setPlaybackRow(Row row) {
mFragment.setPlaybackRow(row);
}
+
+ @Override
+ public void fadeOut() {
+ mFragment.fadeOut();
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackGlue.java
deleted file mode 100644
index 2243186..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackGlue.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Row;
-import android.view.View;
-
-/**
- * Base class for {@link PlaybackControlGlue}.
- */
-public abstract class PlaybackGlue {
- private final Context mContext;
- private PlaybackGlueHost mPlaybackGlueHost;
-
- /**
- * Returns true when the media player is ready to start media playback. Subclasses must
- * implement this method correctly.
- */
- public boolean isReadyForPlayback() { return true; }
-
- /**
- * Interface to allow clients to take action once the video is ready to play.
- */
- public static abstract class PlayerCallback {
- /**
- * This method is fired when the video is ready for playback.
- */
- public abstract void onReadyForPlayback();
- }
-
- /**
- * Lifecycle callbacks triggered by the host(fragment e.g.) hosting the video controls/surface.
- */
- public static abstract class HostLifecycleCallback {
- /**
- * Callback triggered once the host(fragment) has started.
- */
- public abstract void onHostStart();
-
- /**
- * Callback triggered once the host(fragment) has finished.
- */
- public abstract void onHostStop();
- }
-
- /**
- * Sets the {@link PlayerCallback} callback.
- */
- public void setPlayerCallback(MediaPlayerGlue.PlayerCallback mPlayerCallback) {}
-
- /**
- * Starts the media player.
- */
- public void play() {}
-
- /**
- * Pauses the media player.
- */
- public void pause() {}
-
- /**
- * Goes to the next media item.
- */
- public void next() {}
-
- /**
- * Goes to the previous media item.
- */
- public void previous() {}
-
- /**
- * This class represents the UI {@link PlaybackFragment} hosting playback controls and
- * defines the interaction between {@link PlaybackGlue} and the host.
- */
- public static class PlaybackGlueHost {
- PlaybackGlue mGlue;
-
- /**
- * Enables or disables view fading. If enabled, the view will be faded in when the
- * fragment starts and will fade out after a time period.
- */
- public void setFadingEnabled(boolean enable) {
- }
-
- /**
- * Sets the {@link android.view.View.OnKeyListener} on the host. This would trigger
- * the listener when a {@link android.view.KeyEvent} is unhandled by the host.
- */
- public void setOnKeyInterceptListener(View.OnKeyListener onKeyListener) {
- }
-
- /**
- * Sets the {@link android.view.View.OnClickListener} on this fragment.
- */
- public void setOnActionClickedListener(OnActionClickedListener listener) {}
-
- /**
- * Sets the host {@link HostLifecycleCallback} callback on the host.
- */
- public void setHostLifeCycleCallback(HostLifecycleCallback callback) {
- }
-
- /**
- * Notifies host about a change so it can update the view.
- */
- public void notifyPlaybackRowChanged() {}
-
- /**
- * Sets {@link PlaybackRowPresenter} for rendering the playback controls.
- */
- public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {}
-
- /**
- * Sets the {@link Row} that represents the information on control items that needs
- * to be rendered.
- */
- public void setPlaybackRow(Row row) {}
-
- final void attachToGlue(PlaybackGlue glue) {
- if (mGlue != null) {
- mGlue.onDetachedFromHost();
- }
- mGlue = glue;
- }
- }
-
- /**
- * Constructor.
- */
- public PlaybackGlue(Context context) {
- this.mContext = context;
- }
-
- /**
- * Returns the context.
- */
- public Context getContext() {
- return mContext;
- }
-
- /**
- * This method is used to configure the {@link PlaybackGlueHost} with required listeners
- * and presenters.
- */
- public void setHost(PlaybackGlueHost host) {
- mPlaybackGlueHost = host;
- if (host != null) {
- host.attachToGlue(this);
- }
- }
-
- /**
- * This method is called when we try to attach a new host by calling
- * {@link #setHost(PlaybackGlueHost)}.
- */
- public void onDetachedFromHost() {
- mPlaybackGlueHost = null;
- }
-
- /**
- * @return Associated {@link PlaybackGlueHost} or null if not attached to host.
- */
- public PlaybackGlueHost getHost() {
- return mPlaybackGlueHost;
- }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index 32b9c92..33e35eb 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -27,6 +27,7 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
@@ -123,6 +124,7 @@
private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
boolean mResetControlsToPrimaryActionsPending;
+ PlaybackGlueHost.HostCallback mHostCallback;
private final Animator.AnimatorListener mFadeListener =
new Animator.AnimatorListener() {
@@ -323,6 +325,30 @@
fade(false);
}
+ /**
+ * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
+ * take appropriate actions to take action when the hosting fragment starts/stops processing.
+ */
+ void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+ this.mHostCallback = hostCallback;
+ }
+
+ @Override
+ public void onStop() {
+ if (mHostCallback != null) {
+ mHostCallback.onHostStop();
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ if (mHostCallback != null) {
+ mHostCallback.onHostPause();
+ }
+ super.onPause();
+ }
+
private boolean areControlsHidden() {
return mFadingStatus == IDLE && mBgAlpha == 0;
}
@@ -382,6 +408,9 @@
}
getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+ if (mHostCallback != null) {
+ mHostCallback.onHostResume();
+ }
}
void startFadeTimer() {
@@ -765,6 +794,9 @@
super.onStart();
// Workaround problem VideoView forcing itself to focused, let controls take focus.
getRowsFragment().getView().requestFocus();
+ if (mHostCallback != null) {
+ mHostCallback.onHostStart();
+ }
}
private final DataObserver mObserver = new DataObserver() {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
index c1d1452..d41d65f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -30,6 +30,7 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
@@ -126,6 +127,7 @@
private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
boolean mResetControlsToPrimaryActionsPending;
+ PlaybackGlueHost.HostCallback mHostCallback;
private final Animator.AnimatorListener mFadeListener =
new Animator.AnimatorListener() {
@@ -326,6 +328,30 @@
fade(false);
}
+ /**
+ * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
+ * take appropriate actions to take action when the hosting fragment starts/stops processing.
+ */
+ void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+ this.mHostCallback = hostCallback;
+ }
+
+ @Override
+ public void onStop() {
+ if (mHostCallback != null) {
+ mHostCallback.onHostStop();
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ if (mHostCallback != null) {
+ mHostCallback.onHostPause();
+ }
+ super.onPause();
+ }
+
private boolean areControlsHidden() {
return mFadingStatus == IDLE && mBgAlpha == 0;
}
@@ -385,6 +411,9 @@
}
getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+ if (mHostCallback != null) {
+ mHostCallback.onHostResume();
+ }
}
void startFadeTimer() {
@@ -768,6 +797,9 @@
super.onStart();
// Workaround problem VideoView forcing itself to focused, let controls take focus.
getRowsSupportFragment().getView().requestFocus();
+ if (mHostCallback != null) {
+ mHostCallback.onHostStart();
+ }
}
private final DataObserver mObserver = new DataObserver() {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
index f01313d..07701f9 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
@@ -31,8 +31,10 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
@@ -77,10 +79,11 @@
* A dark translucent background.
*/
public static final int BG_DARK = 1;
- private PlaybackGlue.HostLifecycleCallback mHostLifecycleCallback;
+ private PlaybackGlueHost.HostCallback mHostCallback;
/**
* Resets the focus on the button in the middle of control row.
+ * @hide
*/
public void resetFocus() {
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
@@ -112,25 +115,43 @@
private ObjectAdapter mAdapter;
private PlaybackRowPresenter mPresenter;
private Row mRow;
+ private BaseOnItemViewSelectedListener mExternalItemSelectedListener;
private BaseOnItemViewClickedListener mExternalItemClickedListener;
private BaseOnItemViewClickedListener mPlaybackItemClickedListener;
- private BaseOnItemViewClickedListener mOnItemViewClickedListener = new BaseOnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder,
- Object item,
- RowPresenter.ViewHolder rowViewHolder,
- Object row) {
- if (mPlaybackItemClickedListener != null
- && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
- mPlaybackItemClickedListener.onItemClicked(
- itemViewHolder, item, rowViewHolder, row);
- }
- if (mExternalItemClickedListener != null) {
- mExternalItemClickedListener.onItemClicked(
- itemViewHolder, item, rowViewHolder, row);
- }
- }
- };
+
+ private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
+ new BaseOnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder,
+ Object item,
+ RowPresenter.ViewHolder rowViewHolder,
+ Object row) {
+ if (mPlaybackItemClickedListener != null
+ && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
+ mPlaybackItemClickedListener.onItemClicked(
+ itemViewHolder, item, rowViewHolder, row);
+ }
+ if (mExternalItemClickedListener != null) {
+ mExternalItemClickedListener.onItemClicked(
+ itemViewHolder, item, rowViewHolder, row);
+ }
+ }
+ };
+
+ private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
+ new BaseOnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder,
+ Object item,
+ RowPresenter.ViewHolder rowViewHolder,
+ Object row) {
+ if (mExternalItemSelectedListener != null) {
+ mExternalItemSelectedListener.onItemSelected(
+ itemViewHolder, item, rowViewHolder, row);
+ }
+ }
+ };
+
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
public ObjectAdapter getAdapter() {
@@ -140,6 +161,7 @@
/**
* Listener allowing the application to receive notification of fade in and/or fade out
* completion events.
+ * @hide
*/
public static class OnFadeCompleteListener {
public void onFadeInComplete() {
@@ -298,6 +320,7 @@
/**
* Sets the listener to be called when fade in or out has completed.
+ * @hide
*/
public void setFadeCompleteListener(OnFadeCompleteListener listener) {
mFadeCompleteListener = listener;
@@ -305,6 +328,7 @@
/**
* Returns the listener to be called when fade in or out has completed.
+ * @hide
*/
public OnFadeCompleteListener getFadeCompleteListener() {
return mFadeCompleteListener;
@@ -318,13 +342,6 @@
}
/**
- * Returns the input event handler.
- */
- public final View.OnKeyListener getEventHandler() {
- return mInputEventHandler;
- }
-
- /**
* Tickles the playback controls. Fades in the view if it was faded out,
* otherwise resets the fade out timer. Tickling on input events is handled
* by the fragment.
@@ -412,6 +429,9 @@
}
getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+ if (mHostCallback != null) {
+ mHostCallback.onHostResume();
+ }
}
private void startFadeTimer() {
@@ -745,6 +765,7 @@
} else {
mRowsSupportFragment.setAdapter(mAdapter);
}
+ mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
mBgAlpha = 255;
@@ -754,11 +775,11 @@
}
/**
- * Sets the {@link PlaybackGlue.HostLifecycleCallback}. Implementor of this interface will
+ * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
* take appropriate actions to take action when the hosting fragment starts/stops processing.
*/
- public void setHostLifecycleCallback(PlaybackGlue.HostLifecycleCallback hostLifecycleCallback) {
- this.mHostLifecycleCallback = hostLifecycleCallback;
+ public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+ this.mHostCallback = hostCallback;
}
@Override
@@ -766,17 +787,34 @@
super.onStart();
setupChildFragmentLayout();
mRowsSupportFragment.setAdapter(mAdapter);
- if (mHostLifecycleCallback != null) {
- mHostLifecycleCallback.onHostStart();
+ if (mHostCallback != null) {
+ mHostCallback.onHostStart();
}
}
@Override
public void onStop() {
- super.onStop();
- if (mHostLifecycleCallback != null) {
- mHostLifecycleCallback.onHostStop();
+ if (mHostCallback != null) {
+ mHostCallback.onHostStop();
}
+ super.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ if (mHostCallback != null) {
+ mHostCallback.onHostPause();
+ }
+ super.onPause();
+ }
+
+ /**
+ * This listener is called every time there is a selection in {@link RowsSupportFragment}. This can
+ * be used by users to take additional actions such as animations.
+ * @hide
+ */
+ public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
+ mExternalItemSelectedListener = listener;
}
/**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
index 3227747..da644ae 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
@@ -16,6 +16,7 @@
*/
package android.support.v17.leanback.app;
+import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
@@ -26,10 +27,10 @@
import android.view.View;
/**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
* the interaction between this class and {@link PlaybackSupportFragment}.
*/
-public class PlaybackSupportFragmentGlueHost extends PlaybackGlue.PlaybackGlueHost {
+public class PlaybackSupportFragmentGlueHost extends PlaybackGlueHost {
private final PlaybackSupportFragment mFragment;
public PlaybackSupportFragmentGlueHost(PlaybackSupportFragment fragment) {
@@ -48,20 +49,24 @@
@Override
public void setOnActionClickedListener(final OnActionClickedListener listener) {
- mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Action) {
- listener.onActionClicked((Action)item);
+ if (listener == null) {
+ mFragment.setOnPlaybackItemViewClickedListener(null);
+ } else {
+ mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ if (item instanceof Action) {
+ listener.onActionClicked((Action) item);
+ }
}
- }
- });
+ });
+ }
}
@Override
- public void setHostLifeCycleCallback(PlaybackGlue.HostLifecycleCallback callback) {
- mFragment.setHostLifecycleCallback(callback);
+ public void setHostCallback(HostCallback callback) {
+ mFragment.setHostCallback(callback);
}
@Override
@@ -78,4 +83,9 @@
public void setPlaybackRow(Row row) {
mFragment.setPlaybackRow(row);
}
+
+ @Override
+ public void fadeOut() {
+ mFragment.fadeOut();
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index ca486ed..d3a45a0 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -133,12 +133,13 @@
static final String TAG = "RowsFragment";
static final boolean DEBUG = false;
+ static final int ALIGN_TOP_NOT_SET = Integer.MIN_VALUE;
ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
private int mSubPosition;
boolean mExpand = true;
boolean mViewsCreated;
- private int mAlignedTop;
+ private int mAlignedTop = ALIGN_TOP_NOT_SET;
boolean mAfterEntranceTransition = true;
BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
@@ -540,6 +541,9 @@
@Override
public void setAlignment(int windowAlignOffsetFromTop) {
+ if (windowAlignOffsetFromTop == ALIGN_TOP_NOT_SET) {
+ return;
+ }
mAlignedTop = windowAlignOffsetFromTop;
final VerticalGridView gridView = getVerticalGridView();
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index d6c8d3f..5582644 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -136,12 +136,13 @@
static final String TAG = "RowsSupportFragment";
static final boolean DEBUG = false;
+ static final int ALIGN_TOP_NOT_SET = Integer.MIN_VALUE;
ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
private int mSubPosition;
boolean mExpand = true;
boolean mViewsCreated;
- private int mAlignedTop;
+ private int mAlignedTop = ALIGN_TOP_NOT_SET;
boolean mAfterEntranceTransition = true;
BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
@@ -543,6 +544,9 @@
@Override
public void setAlignment(int windowAlignOffsetFromTop) {
+ if (windowAlignOffsetFromTop == ALIGN_TOP_NOT_SET) {
+ return;
+ }
mAlignedTop = windowAlignOffsetFromTop;
final VerticalGridView gridView = getVerticalGridView();
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SurfaceHolderGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/SurfaceHolderGlueHost.java
deleted file mode 100644
index 64498f6..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/SurfaceHolderGlueHost.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- *
- */
-
-package android.support.v17.leanback.app;
-
-import android.view.SurfaceHolder;
-
-/**
- * Interface to be implemented by any host that contains a {@link android.view.SurfaceView}. This
- * will allow MediaPlayerGlue to setup the surface holder callback on the host.
- */
-public interface SurfaceHolderGlueHost {
- /**
- * Sets the {@link SurfaceHolder.Callback} on the the host.
- */
- void setSurfaceHolderCallback(SurfaceHolder.Callback callback);
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index 0db2a38..0b4e34b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -13,10 +13,12 @@
*/
package android.support.v17.leanback.app;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.media.SurfaceHolderGlueHost;
import android.view.SurfaceHolder;
/**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
* the interaction between this class and {@link VideoFragment}.
* @hide
*/
@@ -31,7 +33,7 @@
/**
* Sets the {@link android.view.SurfaceHolder.Callback} on the host.
- * {@link PlaybackGlue.PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
+ * {@link PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
* have a reference to the component hosting it for rendering the video.
*/
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
index eb9d9d4..83769ab 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
@@ -16,10 +16,12 @@
*/
package android.support.v17.leanback.app;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.media.SurfaceHolderGlueHost;
import android.view.SurfaceHolder;
/**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
* the interaction between this class and {@link VideoSupportFragment}.
* @hide
*/
@@ -34,7 +36,7 @@
/**
* Sets the {@link android.view.SurfaceHolder.Callback} on the host.
- * {@link PlaybackGlue.PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
+ * {@link PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
* have a reference to the component hosting it for rendering the video.
*/
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java b/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
new file mode 100644
index 0000000..730bf3a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+/**
+ * A helper class for implementing a glue layer for {@link MediaControllerCompat}.
+ */
+public abstract class MediaControllerGlue extends PlaybackControlGlue {
+ static final String TAG = "MediaControllerGlue";
+ static final boolean DEBUG = false;
+
+ MediaControllerCompat mMediaController;
+
+ private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() {
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ if (DEBUG) Log.v(TAG, "onMetadataChanged");
+ MediaControllerGlue.this.onMetadataChanged();
+ }
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ if (DEBUG) Log.v(TAG, "onPlaybackStateChanged");
+ onStateChanged();
+ }
+ @Override
+ public void onSessionDestroyed() {
+ if (DEBUG) Log.v(TAG, "onSessionDestroyed");
+ mMediaController = null;
+ }
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (DEBUG) Log.v(TAG, "onSessionEvent");
+ }
+ };
+
+ /**
+ * Constructor for the glue.
+ *
+ * @param context
+ * @param fastForwardSpeeds Array of seek speeds for fast forward.
+ * @param rewindSpeeds Array of seek speeds for rewind.
+ */
+ public MediaControllerGlue(Context context,
+ int[] fastForwardSpeeds,
+ int[] rewindSpeeds) {
+ super(context, fastForwardSpeeds, rewindSpeeds);
+ }
+
+ /**
+ * Attaches to the given media controller.
+ */
+ public void attachToMediaController(MediaControllerCompat mediaController) {
+ if (mediaController != mMediaController) {
+ if (DEBUG) Log.v(TAG, "New media controller " + mediaController);
+ detach();
+ mMediaController = mediaController;
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mCallback);
+ }
+ onMetadataChanged();
+ onStateChanged();
+ }
+ }
+
+ /**
+ * Detaches from the media controller. Must be called when the object is no longer
+ * needed.
+ */
+ public void detach() {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mCallback);
+ }
+ mMediaController = null;
+ }
+
+ /**
+ * Returns the media controller currently attached.
+ */
+ public final MediaControllerCompat getMediaController() {
+ return mMediaController;
+ }
+
+ @Override
+ public boolean hasValidMedia() {
+ return mMediaController != null && mMediaController.getMetadata() != null;
+ }
+
+ @Override
+ public boolean isMediaPlaying() {
+ return mMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
+ }
+
+ @Override
+ public int getCurrentSpeedId() {
+ int speed = (int) mMediaController.getPlaybackState().getPlaybackSpeed();
+ if (speed == 0) {
+ return PLAYBACK_SPEED_PAUSED;
+ } else if (speed == 1) {
+ return PLAYBACK_SPEED_NORMAL;
+ } else if (speed > 0) {
+ int[] seekSpeeds = getFastForwardSpeeds();
+ for (int index = 0; index < seekSpeeds.length; index++) {
+ if (speed == seekSpeeds[index]) {
+ return PLAYBACK_SPEED_FAST_L0 + index;
+ }
+ }
+ } else {
+ int[] seekSpeeds = getRewindSpeeds();
+ for (int index = 0; index < seekSpeeds.length; index++) {
+ if (-speed == seekSpeeds[index]) {
+ return -PLAYBACK_SPEED_FAST_L0 - index;
+ }
+ }
+ }
+ Log.w(TAG, "Couldn't find index for speed " + speed);
+ return PLAYBACK_SPEED_INVALID;
+ }
+
+ @Override
+ public CharSequence getMediaTitle() {
+ return mMediaController.getMetadata().getDescription().getTitle();
+ }
+
+ @Override
+ public CharSequence getMediaSubtitle() {
+ return mMediaController.getMetadata().getDescription().getSubtitle();
+ }
+
+ @Override
+ public int getMediaDuration() {
+ return (int) mMediaController.getMetadata().getLong(
+ MediaMetadataCompat.METADATA_KEY_DURATION);
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ return (int) mMediaController.getPlaybackState().getPosition();
+ }
+
+ @Override
+ public Drawable getMediaArt() {
+ Bitmap bitmap = mMediaController.getMetadata().getDescription().getIconBitmap();
+ return bitmap == null ? null : new BitmapDrawable(getContext().getResources(), bitmap);
+ }
+
+ @Override
+ public long getSupportedActions() {
+ long result = 0;
+ long actions = mMediaController.getPlaybackState().getActions();
+ if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
+ result |= ACTION_PLAY_PAUSE;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
+ result |= ACTION_SKIP_TO_NEXT;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ result |= ACTION_SKIP_TO_PREVIOUS;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
+ result |= ACTION_FAST_FORWARD;
+ }
+ if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
+ result |= ACTION_REWIND;
+ }
+ return result;
+ }
+
+ @Override
+ public void play(int speed) {
+ if (DEBUG) Log.v(TAG, "startPlayback speed " + speed);
+ if (speed == PLAYBACK_SPEED_NORMAL) {
+ mMediaController.getTransportControls().play();
+ } else if (speed > 0) {
+ mMediaController.getTransportControls().fastForward();
+ } else {
+ mMediaController.getTransportControls().rewind();
+ }
+ }
+
+ @Override
+ public void pause() {
+ if (DEBUG) Log.v(TAG, "pausePlayback");
+ mMediaController.getTransportControls().pause();
+ }
+
+ @Override
+ public void next() {
+ if (DEBUG) Log.v(TAG, "skipToNext");
+ mMediaController.getTransportControls().skipToNext();
+ }
+
+ @Override
+ public void previous() {
+ if (DEBUG) Log.v(TAG, "skipToPrevious");
+ mMediaController.getTransportControls().skipToPrevious();
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/MediaPlayerGlue.java b/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
similarity index 80%
rename from v17/leanback/src/android/support/v17/leanback/app/MediaPlayerGlue.java
rename to v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
index e06316c..abee2dc 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/MediaPlayerGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
@@ -1,19 +1,20 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-package android.support.v17.leanback.app;
+package android.support.v17.leanback.media;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -23,10 +24,8 @@
import android.os.Handler;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
@@ -38,8 +37,8 @@
import java.io.IOException;
/**
- * This glue extends the {@link PlaybackControlGlue} with a {@link MediaPlayer} synchronization. It
- * supports 7 actions:
+ * This glue extends the {@link android.support.v17.leanback.media.PlaybackControlGlue} with a
+ * {@link MediaPlayer} synchronization. It supports 7 actions:
*
* <ul>
* <li>{@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction}</li>
@@ -64,10 +63,8 @@
private static final String TAG = "MediaPlayerGlue";
protected final PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
protected final PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
- private final Context mContext;
MediaPlayer mPlayer = new MediaPlayer();
private final PlaybackControlsRow.RepeatAction mRepeatAction;
- private PlaybackControlsRow mControlsRow;
private Runnable mRunnable;
private Handler mHandler = new Handler();
private boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized
@@ -113,32 +110,31 @@
/**
* Constructor.
*/
- public MediaPlayerGlue(Context context, PlaybackGlueHost host) {
- this(context, host, new int[]{1}, new int[]{1});
+ public MediaPlayerGlue(Context context) {
+ this(context, new int[]{1}, new int[]{1});
}
/**
* Constructor.
*/
public MediaPlayerGlue(
- Context context, PlaybackGlueHost host, int[] fastForwardSpeeds, int[] rewindSpeeds) {
- super(context, host, fastForwardSpeeds, rewindSpeeds);
- mContext = context;
+ Context context, int[] fastForwardSpeeds, int[] rewindSpeeds) {
+ super(context, fastForwardSpeeds, rewindSpeeds);
// Instantiate secondary actions
- mRepeatAction = new PlaybackControlsRow.RepeatAction(mContext);
- mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(mContext);
- mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(mContext);
+ mRepeatAction = new PlaybackControlsRow.RepeatAction(getContext());
+ mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(getContext());
+ mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(getContext());
mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
}
@Override
- public void setHost(PlaybackGlueHost host) {
- super.setHost(host);
- if (getHost() instanceof SurfaceHolderGlueHost) {
- ((SurfaceHolderGlueHost) getHost()).setSurfaceHolderCallback(
- new VideoFragmentSurfaceHolderCallback());
+ protected void onAttachedToHost(PlaybackGlueHost host) {
+ super.onAttachedToHost(host);
+ if (host instanceof SurfaceHolderGlueHost) {
+ ((SurfaceHolderGlueHost) host).setSurfaceHolderCallback(
+ new VideoPlayerSurfaceHolderCallback());
}
}
@@ -159,23 +155,22 @@
mPlayer.reset();
}
+ /**
+ * Release internal MediaPlayer. Should not use the object after call release().
+ */
public void release() {
mPlayer.release();
}
@Override
- public void onDetachedFromHost() {
+ protected void onDetachedFromHost() {
super.onDetachedFromHost();
reset();
release();
}
- /**
- * Override this method in case you need to add different secondary actions.
- *
- * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
- */
- protected void addSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
+ @Override
+ protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
secondaryActionsAdapter.add(mRepeatAction);
secondaryActionsAdapter.add(mThumbsDownAction);
secondaryActionsAdapter.add(mThumbsUpAction);
@@ -188,22 +183,6 @@
mPlayer.setDisplay(surfaceHolder);
}
- /**
- * Creates a presenter ({@link PlaybackControlsRowPresenter}) for rendering playback controls.
- * @return
- */
- public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
- PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
- mControlsRow = getControlsRow();
-
- // Add secondary actions and change the control row color.
- ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
- new ControlButtonPresenterSelector());
- mControlsRow.setSecondaryActionsAdapter(secondaryActions);
- addSecondaryActions(secondaryActions);
- return presenter;
- }
-
@Override
public void enableProgressUpdating(final boolean enabled) {
if (!enabled) {
@@ -325,27 +304,7 @@
}
@Override
- protected void startPlayback(int speed) throws IllegalStateException {
- play();
- }
-
- @Override
- protected void pausePlayback() {
- pause();
- }
-
- @Override
- protected void skipToNext() {
- // Not supported.
- }
-
- @Override
- protected void skipToPrevious() {
- // Not supported.
- }
-
- @Override
- public void play() {
+ public void play(int speed) {
mPlayer.start();
onMetadataChanged();
onStateChanged();
@@ -477,7 +436,7 @@
mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
- mControlsRow.setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
+ getControlsRow().setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
}
});
mPlayer.prepareAsync();
@@ -485,23 +444,12 @@
}
/**
- * Call to <code>startPlayback(1)</code>.
- *
- * @throws IllegalStateException See {@link MediaPlayer} for further information about it's
- * different states when setting a data source and preparing it
- * to be played.
- */
- public void startPlayback() throws IllegalStateException {
- startPlayback(1);
- }
-
- /**
- * This is a listener implementation for the {@link OnItemViewSelectedListener} of the {@link
- * PlaybackFragment}. This implementation is required in order to detect KEY_DOWN events
+ * This is a listener implementation for the {@link OnItemViewSelectedListener}.
+ * This implementation is required in order to detect KEY_DOWN events
* on the {@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction} and
* {@link android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction}. Thus you
* should <u>NOT</u> set another {@link OnItemViewSelectedListener} on your
- * {@link PlaybackFragment}. Instead, override this method and call its super (this)
+ * Fragment. Instead, override this method and call its super (this)
* implementation.
*
* @see OnItemViewSelectedListener#onItemSelected(
@@ -523,10 +471,10 @@
}
/**
- * Implements {@link SurfaceHolder.Callback} that can then be set on the \
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}
+ * Implements {@link SurfaceHolder.Callback} that can then be set on the
+ * {@link PlaybackGlueHost}.
*/
- private class VideoFragmentSurfaceHolderCallback implements SurfaceHolder.Callback {
+ class VideoPlayerSurfaceHolderCallback implements SurfaceHolder.Callback {
private boolean mMediaPlayerReset = true;
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
new file mode 100644
index 0000000..bdd67fb
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
@@ -0,0 +1,821 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A helper class for managing a {@link PlaybackControlsRow}
+ * and {@link PlaybackGlueHost} that implements a
+ * recommended approach to handling standard playback control actions such as play/pause,
+ * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
+ * is a glue layer in that manages the configuration of and interaction between the
+ * leanback UI components by defining a functional interface to the media player.
+ *
+ * <p>You can instantiate a concrete subclass such as MediaPlayerGlue or you must
+ * subclass this abstract helper. To create a subclass you must implement all of the
+ * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
+ * {@link #onStateChanged()} appropriately.
+ * </p>
+ *
+ * <p>To use an instance of the glue layer, first construct an instance. Constructor parameters
+ * inform the glue what speed levels are supported for fast forward/rewind.
+ * </p>
+ *
+ * <p>You may override {@link #onCreateControlsRowAndPresenter()} which will set a controls
+ * row and return a row presenter you can use to present the row.
+ * </p>
+ *
+ * <p>The helper sets a {@link SparseArrayObjectAdapter}
+ * on the controls row as the primary actions adapter, and adds actions to it. You can provide
+ * additional actions by overriding {@link #onCreatePrimaryActions}. This helper does not
+ * deal in secondary actions so those you may add separately.
+ * </p>
+ *
+ * <p>Provide a click listener on your fragment and if an action is clicked, call
+ * {@link #onActionClicked}.
+ * </p>
+ *
+ * <p>This helper implements a key event handler. If you pass a
+ * {@link PlaybackGlueHost}, it will configure it's
+ * fragment to intercept all key events. Otherwise, you should set the glue object as key event
+ * handler to the ViewHolder when bound by your row presenter; see
+ * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
+ * </p>
+ *
+ * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
+ * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
+ * {@link #getUpdatePeriod()} provides a recommended update period.
+ * </p>
+ *
+ */
+public abstract class PlaybackControlGlue extends PlaybackGlue
+ implements OnActionClickedListener, View.OnKeyListener {
+ /**
+ * The adapter key for the first custom control on the left side
+ * of the predefined primary controls.
+ */
+ public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
+
+ /**
+ * The adapter key for the skip to previous control.
+ */
+ public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
+
+ /**
+ * The adapter key for the rewind control.
+ */
+ public static final int ACTION_REWIND = 0x20;
+
+ /**
+ * The adapter key for the play/pause control.
+ */
+ public static final int ACTION_PLAY_PAUSE = 0x40;
+
+ /**
+ * The adapter key for the fast forward control.
+ */
+ public static final int ACTION_FAST_FORWARD = 0x80;
+
+ /**
+ * The adapter key for the skip to next control.
+ */
+ public static final int ACTION_SKIP_TO_NEXT = 0x100;
+
+ /**
+ * The adapter key for the first custom control on the right side
+ * of the predefined primary controls.
+ */
+ public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
+
+ /**
+ * Invalid playback speed.
+ */
+ public static final int PLAYBACK_SPEED_INVALID = -1;
+
+ /**
+ * Speed representing playback state that is paused.
+ */
+ public static final int PLAYBACK_SPEED_PAUSED = 0;
+
+ /**
+ * Speed representing playback state that is playing normally.
+ */
+ public static final int PLAYBACK_SPEED_NORMAL = 1;
+
+ /**
+ * The initial (level 0) fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L0 = 10;
+
+ /**
+ * The level 1 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L1 = 11;
+
+ /**
+ * The level 2 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L2 = 12;
+
+ /**
+ * The level 3 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L3 = 13;
+
+ /**
+ * The level 4 fast forward playback speed.
+ * The negative of this value is for rewind at the same speed.
+ */
+ public static final int PLAYBACK_SPEED_FAST_L4 = 14;
+
+ static final String TAG = "PlaybackControlGlue";
+ static final boolean DEBUG = false;
+
+ static final int MSG_UPDATE_PLAYBACK_STATE = 100;
+ private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
+ private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
+ - PLAYBACK_SPEED_FAST_L0 + 1;
+
+ private final int[] mFastForwardSpeeds;
+ private final int[] mRewindSpeeds;
+ private PlaybackControlsRow mControlsRow;
+ private PlaybackControlsRowPresenter mControlsRowPresenter;
+ private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
+ private PlaybackControlsRow.SkipNextAction mSkipNextAction;
+ private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
+ private PlaybackControlsRow.FastForwardAction mFastForwardAction;
+ private PlaybackControlsRow.RewindAction mRewindAction;
+ private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ private boolean mFadeWhenPlaying = true;
+
+ static class UpdatePlaybackStateHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
+ PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
+ if (glue != null) {
+ glue.updatePlaybackState();
+ }
+ }
+ }
+ }
+
+ static final Handler sHandler = new UpdatePlaybackStateHandler();
+
+ final WeakReference<PlaybackControlGlue> mGlueWeakReference = new WeakReference(this);
+
+ /**
+ * Constructor for the glue.
+ *
+ * @param context
+ * @param seekSpeeds Array of seek speeds for fast forward and rewind.
+ */
+ public PlaybackControlGlue(Context context, int[] seekSpeeds) {
+ this(context, seekSpeeds, seekSpeeds);
+ }
+
+ /**
+ * Constructor for the glue.
+ *
+ * @param context
+ * @param fastForwardSpeeds Array of seek speeds for fast forward.
+ * @param rewindSpeeds Array of seek speeds for rewind.
+ */
+ public PlaybackControlGlue(Context context,
+ int[] fastForwardSpeeds,
+ int[] rewindSpeeds) {
+ super(context);
+ if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+ throw new IllegalStateException("invalid fastForwardSpeeds array size");
+ }
+ mFastForwardSpeeds = fastForwardSpeeds;
+ if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+ throw new IllegalStateException("invalid rewindSpeeds array size");
+ }
+ mRewindSpeeds = rewindSpeeds;
+ }
+
+ @Override
+ protected void onAttachedToHost(PlaybackGlueHost host) {
+ super.onAttachedToHost(host);
+ host.setOnKeyInterceptListener(this);
+ host.setOnActionClickedListener(this);
+ if (getControlsRow() == null || getControlsRowPresenter() == null) {
+ onCreateControlsRowAndPresenter();
+ }
+ host.setPlaybackRowPresenter(getControlsRowPresenter());
+ host.setPlaybackRow(getControlsRow());
+ }
+
+ @Override
+ protected void onHostStart() {
+ enableProgressUpdating(true);
+ }
+
+ @Override
+ protected void onHostStop() {
+ enableProgressUpdating(false);
+ }
+
+ /**
+ * Instantiating a {@link PlaybackControlsRow} and corresponding
+ * {@link PlaybackControlsRowPresenter}. Subclass may override.
+ */
+ protected void onCreateControlsRowAndPresenter() {
+ PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
+ setControlsRow(controlsRow);
+
+ final AbstractDetailsDescriptionPresenter detailsPresenter =
+ new AbstractDetailsDescriptionPresenter() {
+ @Override
+ protected void onBindDescription(ViewHolder
+ viewHolder, Object object) {
+ PlaybackControlGlue glue = (PlaybackControlGlue) object;
+ if (glue.hasValidMedia()) {
+ viewHolder.getTitle().setText(glue.getMediaTitle());
+ viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
+ } else {
+ viewHolder.getTitle().setText("");
+ viewHolder.getSubtitle().setText("");
+ }
+ }
+ };
+
+ setControlsRowPresenter(new PlaybackControlsRowPresenter(detailsPresenter) {
+ @Override
+ protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+ super.onBindRowViewHolder(vh, item);
+ vh.setOnKeyListener(PlaybackControlGlue.this);
+ }
+ @Override
+ protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
+ super.onUnbindRowViewHolder(vh);
+ vh.setOnKeyListener(null);
+ }
+ });
+ }
+
+ /**
+ * Returns the fast forward speeds.
+ */
+ public int[] getFastForwardSpeeds() {
+ return mFastForwardSpeeds;
+ }
+
+ /**
+ * Returns the rewind speeds.
+ */
+ public int[] getRewindSpeeds() {
+ return mRewindSpeeds;
+ }
+
+ /**
+ * Sets the controls to fade after a timeout when media is playing.
+ */
+ public void setFadingEnabled(boolean enable) {
+ mFadeWhenPlaying = enable;
+ if (!mFadeWhenPlaying && getHost() != null) {
+ getHost().setFadingEnabled(false);
+ }
+ }
+
+ /**
+ * Returns true if controls are set to fade when media is playing.
+ */
+ public boolean isFadingEnabled() {
+ return mFadeWhenPlaying;
+ }
+
+ /**
+ * Sets the controls row to be managed by the glue layer.
+ * The primary actions and playback state related aspects of the row
+ * are updated by the glue.
+ */
+ public void setControlsRow(PlaybackControlsRow controlsRow) {
+ mControlsRow = controlsRow;
+ mControlsRow.setPrimaryActionsAdapter(
+ createPrimaryActionsAdapter(new ControlButtonPresenterSelector()));
+ // Add secondary actions
+ ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
+ new ControlButtonPresenterSelector());
+ onCreateSecondaryActions(secondaryActions);
+ getControlsRow().setSecondaryActionsAdapter(secondaryActions);
+ updateControlsRow();
+ }
+
+ /**
+ * @hide
+ */
+ protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+ PresenterSelector presenterSelector) {
+ SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+ onCreatePrimaryActions(adapter);
+ return adapter;
+ }
+
+ /**
+ * Sets the controls row Presenter to be managed by the glue layer.
+ */
+ public void setControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
+ mControlsRowPresenter = presenter;
+ }
+
+ /**
+ * Returns the playback controls row managed by the glue layer.
+ */
+ public PlaybackControlsRow getControlsRow() {
+ return mControlsRow;
+ }
+
+ /**
+ * Returns the playback controls row Presenter managed by the glue layer.
+ */
+ public PlaybackControlsRowPresenter getControlsRowPresenter() {
+ return mControlsRowPresenter;
+ }
+
+ /**
+ * Override this to start/stop a runnable to call {@link #updateProgress} at
+ * an interval such as {@link #getUpdatePeriod}.
+ */
+ public void enableProgressUpdating(boolean enable) {
+ }
+
+ /**
+ * Returns the time period in milliseconds that should be used
+ * to update the progress. See {@link #updateProgress()}.
+ */
+ public int getUpdatePeriod() {
+ // TODO: calculate a better update period based on total duration and screen size
+ return 500;
+ }
+
+ /**
+ * Updates the progress bar based on the current media playback position.
+ */
+ public void updateProgress() {
+ int position = getCurrentPosition();
+ if (DEBUG) Log.v(TAG, "updateProgress " + position);
+ mControlsRow.setCurrentTime(position);
+ }
+
+ /**
+ * Handles action clicks. A subclass may override this add support for additional actions.
+ */
+ @Override
+ public void onActionClicked(Action action) {
+ dispatchAction(action, null);
+ }
+
+ /**
+ * Handles key events and returns true if handled. A subclass may override this to provide
+ * additional support.
+ */
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_ESCAPE:
+ boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
+ || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
+ if (abortSeek) {
+ mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ play(mPlaybackSpeed);
+ updatePlaybackStatusAfterUserAction();
+ return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
+ }
+ return false;
+ }
+ final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+ mControlsRow.getPrimaryActionsAdapter();
+ Action action = mControlsRow.getActionForKeyCode(primaryActionsAdapter, keyCode);
+
+ if (action != null) {
+ if (action == primaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
+ || action == primaryActionsAdapter.lookup(ACTION_REWIND)
+ || action == primaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
+ || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
+ || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ dispatchAction(action, event);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when the given action is invoked, either by click or keyevent.
+ */
+ boolean dispatchAction(Action action, KeyEvent keyEvent) {
+ boolean handled = false;
+ if (action == mPlayPauseAction) {
+ boolean canPlay = keyEvent == null
+ || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+ || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
+ boolean canPause = keyEvent == null
+ || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+ || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
+ // PLAY_PAUSE PLAY PAUSE
+ // playing paused paused
+ // paused playing playing
+ // ff/rw playing playing paused
+ if (canPause
+ && (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
+ mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
+ mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
+ pause();
+ } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
+ mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+ play(mPlaybackSpeed);
+ }
+ updatePlaybackStatusAfterUserAction();
+ handled = true;
+ } else if (action == mSkipNextAction) {
+ next();
+ handled = true;
+ } else if (action == mSkipPreviousAction) {
+ previous();
+ handled = true;
+ } else if (action == mFastForwardAction) {
+ if (mPlaybackSpeed < getMaxForwardSpeedId()) {
+ switch (mPlaybackSpeed) {
+ case PLAYBACK_SPEED_FAST_L0:
+ case PLAYBACK_SPEED_FAST_L1:
+ case PLAYBACK_SPEED_FAST_L2:
+ case PLAYBACK_SPEED_FAST_L3:
+ mPlaybackSpeed++;
+ break;
+ default:
+ mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
+ break;
+ }
+ play(mPlaybackSpeed);
+ updatePlaybackStatusAfterUserAction();
+ }
+ handled = true;
+ } else if (action == mRewindAction) {
+ if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
+ switch (mPlaybackSpeed) {
+ case -PLAYBACK_SPEED_FAST_L0:
+ case -PLAYBACK_SPEED_FAST_L1:
+ case -PLAYBACK_SPEED_FAST_L2:
+ case -PLAYBACK_SPEED_FAST_L3:
+ mPlaybackSpeed--;
+ break;
+ default:
+ mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
+ break;
+ }
+ play(mPlaybackSpeed);
+ updatePlaybackStatusAfterUserAction();
+ }
+ handled = true;
+ }
+ return handled;
+ }
+
+ private int getMaxForwardSpeedId() {
+ return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
+ }
+
+ private int getMaxRewindSpeedId() {
+ return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
+ }
+
+ private void updateControlsRow() {
+ updateRowMetadata();
+ sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
+ updatePlaybackState();
+ }
+
+ private void updatePlaybackStatusAfterUserAction() {
+ updatePlaybackState(mPlaybackSpeed);
+ // Sync playback state after a delay
+ sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
+ sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
+ mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
+ }
+
+ /**
+ * Start playback at the given speed.
+ *
+ * @param speed The desired playback speed. For normal playback this will be
+ * {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
+ * and negative values for rewind.
+ */
+ public void play(int speed) {
+ }
+
+ @Override
+ public final void play() {
+ play(PLAYBACK_SPEED_NORMAL);
+ }
+
+ private void updateRowMetadata() {
+ if (mControlsRow == null) {
+ return;
+ }
+
+ if (DEBUG) Log.v(TAG, "updateRowMetadata");
+
+ if (!hasValidMedia()) {
+ mControlsRow.setImageDrawable(null);
+ mControlsRow.setTotalTime(0);
+ mControlsRow.setCurrentTime(0);
+ } else {
+ mControlsRow.setImageDrawable(getMediaArt());
+ mControlsRow.setTotalTime(getMediaDuration());
+ mControlsRow.setCurrentTime(getCurrentPosition());
+ }
+
+ if (getHost() != null) {
+ getHost().notifyPlaybackRowChanged();
+ }
+ }
+
+ void updatePlaybackState() {
+ if (hasValidMedia()) {
+ mPlaybackSpeed = getCurrentSpeedId();
+ updatePlaybackState(mPlaybackSpeed);
+ }
+ }
+
+ private void updatePlaybackState(int playbackSpeed) {
+ if (mControlsRow == null) {
+ return;
+ }
+
+ final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+ getControlsRow().getPrimaryActionsAdapter();
+ final long actions = getSupportedActions();
+ if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
+ if (mSkipPreviousAction == null) {
+ mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
+ }
+ primaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
+ } else {
+ primaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
+ mSkipPreviousAction = null;
+ }
+ if ((actions & ACTION_REWIND) != 0) {
+ if (mRewindAction == null) {
+ mRewindAction = new PlaybackControlsRow.RewindAction(
+ getContext(),
+ mRewindSpeeds.length);
+ }
+ primaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
+ } else {
+ primaryActionsAdapter.clear(ACTION_REWIND);
+ mRewindAction = null;
+ }
+ if ((actions & ACTION_PLAY_PAUSE) != 0) {
+ if (mPlayPauseAction == null) {
+ mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
+ }
+ primaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
+ } else {
+ primaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
+ mPlayPauseAction = null;
+ }
+ if ((actions & ACTION_FAST_FORWARD) != 0) {
+ if (mFastForwardAction == null) {
+ mFastForwardAction = new PlaybackControlsRow.FastForwardAction(
+ getContext(),
+ mFastForwardSpeeds.length);
+ }
+ primaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
+ } else {
+ primaryActionsAdapter.clear(ACTION_FAST_FORWARD);
+ mFastForwardAction = null;
+ }
+ if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
+ if (mSkipNextAction == null) {
+ mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
+ }
+ primaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
+ } else {
+ primaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
+ mSkipNextAction = null;
+ }
+
+ if (mFastForwardAction != null) {
+ int index = 0;
+ if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
+ index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
+ }
+ if (mFastForwardAction.getIndex() != index) {
+ mFastForwardAction.setIndex(index);
+ notifyItemChanged(primaryActionsAdapter, mFastForwardAction);
+ }
+ }
+ if (mRewindAction != null) {
+ int index = 0;
+ if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
+ index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
+ }
+ if (mRewindAction.getIndex() != index) {
+ mRewindAction.setIndex(index);
+ notifyItemChanged(primaryActionsAdapter, mRewindAction);
+ }
+ }
+
+ if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
+ updateProgress();
+ enableProgressUpdating(false);
+ } else {
+ enableProgressUpdating(true);
+ }
+
+ if (mFadeWhenPlaying && getHost() != null) {
+ getHost().setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
+ }
+
+ if (mPlayPauseAction != null) {
+ int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
+ ? PlaybackControlsRow.PlayPauseAction.PLAY
+ : PlaybackControlsRow.PlayPauseAction.PAUSE;
+ if (mPlayPauseAction.getIndex() != index) {
+ mPlayPauseAction.setIndex(index);
+ notifyItemChanged(primaryActionsAdapter, mPlayPauseAction);
+ }
+ }
+ }
+
+ private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
+ int index = adapter.indexOf(object);
+ if (index >= 0) {
+ adapter.notifyArrayItemRangeChanged(index, 1);
+ }
+ }
+
+ private static String getSpeedString(int speed) {
+ switch (speed) {
+ case PLAYBACK_SPEED_INVALID:
+ return "PLAYBACK_SPEED_INVALID";
+ case PLAYBACK_SPEED_PAUSED:
+ return "PLAYBACK_SPEED_PAUSED";
+ case PLAYBACK_SPEED_NORMAL:
+ return "PLAYBACK_SPEED_NORMAL";
+ case PLAYBACK_SPEED_FAST_L0:
+ return "PLAYBACK_SPEED_FAST_L0";
+ case PLAYBACK_SPEED_FAST_L1:
+ return "PLAYBACK_SPEED_FAST_L1";
+ case PLAYBACK_SPEED_FAST_L2:
+ return "PLAYBACK_SPEED_FAST_L2";
+ case PLAYBACK_SPEED_FAST_L3:
+ return "PLAYBACK_SPEED_FAST_L3";
+ case PLAYBACK_SPEED_FAST_L4:
+ return "PLAYBACK_SPEED_FAST_L4";
+ case -PLAYBACK_SPEED_FAST_L0:
+ return "-PLAYBACK_SPEED_FAST_L0";
+ case -PLAYBACK_SPEED_FAST_L1:
+ return "-PLAYBACK_SPEED_FAST_L1";
+ case -PLAYBACK_SPEED_FAST_L2:
+ return "-PLAYBACK_SPEED_FAST_L2";
+ case -PLAYBACK_SPEED_FAST_L3:
+ return "-PLAYBACK_SPEED_FAST_L3";
+ case -PLAYBACK_SPEED_FAST_L4:
+ return "-PLAYBACK_SPEED_FAST_L4";
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if there is a valid media item.
+ */
+ public abstract boolean hasValidMedia();
+
+ /**
+ * Returns true if media is currently playing.
+ */
+ public abstract boolean isMediaPlaying();
+
+ /**
+ * Returns the title of the media item.
+ */
+ public abstract CharSequence getMediaTitle();
+
+ /**
+ * Returns the subtitle of the media item.
+ */
+ public abstract CharSequence getMediaSubtitle();
+
+ /**
+ * Returns the duration of the media item in milliseconds.
+ */
+ public abstract int getMediaDuration();
+
+ /**
+ * Returns a bitmap of the art for the media item.
+ */
+ public abstract Drawable getMediaArt();
+
+ /**
+ * Returns a bitmask of actions supported by the media player.
+ */
+ public abstract long getSupportedActions();
+
+ /**
+ * Returns the current playback speed. When playing normally,
+ * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
+ */
+ public abstract int getCurrentSpeedId();
+
+ /**
+ * Returns the current position of the media item in milliseconds.
+ */
+ public abstract int getCurrentPosition();
+
+ /**
+ * May be overridden to add primary actions to the adapter.
+ *
+ * @param primaryActionsAdapter The adapter to add primary {@link Action}s.
+ */
+ protected void onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter) {
+ }
+
+ /**
+ * May be overridden to add secondary actions to the adapter.
+ *
+ * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
+ */
+ protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
+ }
+
+ /**
+ * Must be called appropriately by a subclass when the playback state has changed.
+ * It updates the playback state displayed on the media player.
+ */
+ protected void onStateChanged() {
+ if (DEBUG) Log.v(TAG, "onStateChanged");
+ // If a pending control button update is present, delay
+ // the update until the state settles.
+ if (!hasValidMedia()) {
+ return;
+ }
+ if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
+ sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
+ if (getCurrentSpeedId() != mPlaybackSpeed) {
+ if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
+ sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
+ mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
+ } else {
+ if (DEBUG) Log.v(TAG, "Update state matches expectation");
+ updatePlaybackState();
+ }
+ } else {
+ updatePlaybackState();
+ }
+ }
+
+ /**
+ * Must be called appropriately by a subclass when the metadata state has changed.
+ */
+ protected void onMetadataChanged() {
+ if (DEBUG) Log.v(TAG, "onMetadataChanged");
+ updateRowMetadata();
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
new file mode 100644
index 0000000..8ed4b93
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.content.Context;
+import android.support.annotation.CallSuper;
+
+/**
+ * Base class for abstraction of media play/pause feature. A subclass of PlaybackGlue will contain
+ * implementation of Media Player. App initializes PlaybackGlue subclass, associated it with a
+ * {@link PlaybackGlueHost}. {@link PlaybackGlueHost} is typically implemented by a Fragment or
+ * an Activity, it provides the environment to render UI for PlaybackGlue object, it optionally
+ * provides SurfaceHolder via {@link SurfaceHolderGlueHost} to render video.
+ *
+ * @see PlaybackGlueHost
+ */
+public abstract class PlaybackGlue {
+ private final Context mContext;
+ private PlaybackGlueHost mPlaybackGlueHost;
+
+ /**
+ * Interface to allow clients to take action once the video is ready to play.
+ */
+ public abstract static class PlayerCallback {
+ /**
+ * This method is fired when the video is ready for playback.
+ */
+ public abstract void onReadyForPlayback();
+ }
+
+ /**
+ * Constructor.
+ */
+ public PlaybackGlue(Context context) {
+ this.mContext = context;
+ }
+
+ /**
+ * Returns the context.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns true when the media player is ready to start media playback. Subclasses must
+ * implement this method correctly.
+ */
+ public boolean isReadyForPlayback() {
+ return true;
+ }
+
+ /**
+ * Sets the {@link PlayerCallback} callback.
+ */
+ public void setPlayerCallback(PlayerCallback playerCallback) {
+ }
+
+ /**
+ * Starts the media player.
+ */
+ public void play() {
+ }
+
+ /**
+ * Pauses the media player.
+ */
+ public void pause() {
+ }
+
+ /**
+ * Goes to the next media item. This method is optional.
+ */
+ public void next() {
+ }
+
+ /**
+ * Goes to the previous media item. This method is optional.
+ */
+ public void previous() {
+ }
+
+ /**
+ * This method is used to configure the {@link PlaybackGlueHost} with required listeners.
+ */
+ public final void setHost(PlaybackGlueHost host) {
+ if (mPlaybackGlueHost == host) {
+ return;
+ }
+ if (mPlaybackGlueHost != null) {
+ mPlaybackGlueHost.attachToGlue(null);
+ }
+ mPlaybackGlueHost = host;
+ if (mPlaybackGlueHost != null) {
+ mPlaybackGlueHost.attachToGlue(this);
+ }
+ }
+
+ /**
+ * This method is called when {@link PlaybackGlueHost is started. Subclass may override.
+ */
+ protected void onHostStart() {
+ }
+
+ /**
+ * This method is called when {@link PlaybackGlueHost is stopped. Subclass may override.
+ */
+ protected void onHostStop() {
+ }
+
+ /**
+ * This method is called when {@link PlaybackGlueHost is resumed. Subclass may override.
+ */
+ protected void onHostResume() {
+ }
+
+ /**
+ * This method is called when {@link PlaybackGlueHost is paused. Subclass may override.
+ */
+ protected void onHostPause() {
+ }
+
+ /**
+ * This method is called attached to associated {@link PlaybackGlueHost}. Subclass may override
+ * and call super.onAttachedToHost().
+ */
+ @CallSuper
+ protected void onAttachedToHost(PlaybackGlueHost host) {
+ mPlaybackGlueHost = host;
+ mPlaybackGlueHost.setHostCallback(new PlaybackGlueHost.HostCallback() {
+ @Override
+ public void onHostStart() {
+ PlaybackGlue.this.onHostStart();
+ }
+
+ @Override
+ public void onHostStop() {
+ PlaybackGlue.this.onHostStop();
+ }
+
+ @Override
+ public void onHostResume() {
+ PlaybackGlue.this.onHostResume();
+ }
+
+ @Override
+ public void onHostPause() {
+ PlaybackGlue.this.onHostPause();
+ }
+ });
+ }
+
+ /**
+ * This method is called when current associated {@link PlaybackGlueHost} is attached to a
+ * different {@link PlaybackGlue}. Subclass may override and call super.onDetachedFromHost()
+ * at last.
+ */
+ @CallSuper
+ protected void onDetachedFromHost() {
+ if (mPlaybackGlueHost != null) {
+ mPlaybackGlueHost.setHostCallback(null);
+ mPlaybackGlueHost = null;
+ }
+ }
+
+ /**
+ * @return Associated {@link PlaybackGlueHost} or null if not attached to host.
+ */
+ public PlaybackGlueHost getHost() {
+ return mPlaybackGlueHost;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
new file mode 100644
index 0000000..3cf086b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.Row;
+import android.view.View;
+
+/**
+ * This class represents the UI (e.g. Fragment/Activity) hosting playback controls and
+ * defines the interaction between {@link PlaybackGlue} and the host.
+ * PlaybackGlueHost provides the following functions:
+ * <li>Render UI of PlaybackGlue: {@link #setPlaybackRow(Row)},
+ * {@link #setPlaybackRowPresenter(PlaybackRowPresenter)}.
+ * </li>
+ * <li>Callback for fragment/activity onStart/onStop: {@link #setHostCallback(HostCallback)}.
+ * </li>
+ * <li>Auto fade out controls after a short period: {@link #setFadingEnabled(boolean)}.
+ * </li>
+ * <li>Key listener and ActionListener. {@link #setOnKeyInterceptListener(View.OnKeyListener)},
+ * {@link #setOnActionClickedListener(OnActionClickedListener)}.
+ * </li>
+ *
+ * Subclass of PlaybackGlueHost may implement optional interface e.g. {@link SurfaceHolderGlueHost}
+ * to provide SurfaceView. These optional interface should be used during
+ * {@link PlaybackGlue#setHost(PlaybackGlueHost)}.
+ */
+public abstract class PlaybackGlueHost {
+ PlaybackGlue mGlue;
+
+ /**
+ * Callbacks triggered by the host(e.g. fragment) hosting the video controls/surface.
+ *
+ * @see #setHostCallback(HostCallback)
+ */
+ public abstract static class HostCallback {
+ /**
+ * Callback triggered once the host(fragment) has started.
+ */
+ public void onHostStart() {
+ }
+
+ /**
+ * Callback triggered once the host(fragment) has stopped.
+ */
+ public void onHostStop() {
+ }
+
+ /**
+ * Callback triggered once the host(fragment) has paused.
+ */
+ public void onHostPause() {
+ }
+
+ /**
+ * Callback triggered once the host(fragment) has resumed.
+ */
+ public void onHostResume() {
+ }
+ }
+
+ /**
+ * Enables or disables view fading. If enabled, the view will be faded in when the
+ * fragment starts and will fade out after a time period.
+ */
+ public void setFadingEnabled(boolean enable) {
+ }
+
+ /**
+ * Fade out views immediately.
+ */
+ public void fadeOut() {
+ }
+
+ /**
+ * Sets the {@link android.view.View.OnKeyListener} on the host. This would trigger
+ * the listener when a {@link android.view.KeyEvent} is unhandled by the host.
+ */
+ public void setOnKeyInterceptListener(View.OnKeyListener onKeyListener) {
+ }
+
+ /**
+ * Sets the {@link View.OnClickListener} on this fragment.
+ */
+ public void setOnActionClickedListener(OnActionClickedListener listener) {}
+
+ /**
+ * Sets the host {@link HostCallback} callback on the host. This method should only be called
+ * by {@link PlaybackGlue}. App should not directly call this method, app should override
+ * {@link PlaybackGlue#onHostStart()} etc.
+ */
+ public void setHostCallback(HostCallback callback) {
+ }
+
+ /**
+ * Notifies host about a change so it can update the view.
+ */
+ public void notifyPlaybackRowChanged() {}
+
+ /**
+ * Sets {@link PlaybackRowPresenter} for rendering the playback controls.
+ */
+ public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {}
+
+ /**
+ * Sets the {@link Row} that represents the information on control items that needs
+ * to be rendered.
+ */
+ public void setPlaybackRow(Row row) {}
+
+ final void attachToGlue(PlaybackGlue glue) {
+ if (mGlue != null) {
+ mGlue.onDetachedFromHost();
+ }
+ mGlue = glue;
+ if (mGlue != null) {
+ mGlue.onAttachedToHost(this);
+ }
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java b/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
new file mode 100644
index 0000000..119626a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.view.SurfaceHolder;
+
+/**
+ * Optional interface to be implemented by any subclass of {@link PlaybackGlueHost} that contains
+ * a {@link android.view.SurfaceView}. This will allow subclass of {@link PlaybackGlue} to setup
+ * the surface holder callback during {@link PlaybackGlue#setHost(PlaybackGlueHost)}.
+ *
+ * @see PlaybackGlue#setHost(PlaybackGlueHost)
+ * @see MediaPlayerGlue
+ */
+public interface SurfaceHolderGlueHost {
+ /**
+ * Sets the {@link SurfaceHolder.Callback} on the the host.
+ */
+ void setSurfaceHolderCallback(SurfaceHolder.Callback callback);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
index ca6ee77..7d2904a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -151,6 +151,9 @@
* @param actions The list of actions to be managed.
*/
public void setActions(List<GuidedAction> actions) {
+ if (!mIsSubAdapter) {
+ mStylist.collapseAction(false);
+ }
mActionOnFocusListener.unFocus();
mActions.clear();
mActions.addAll(actions);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
index 66dc12d..0d2b45e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -18,6 +18,7 @@
import android.graphics.Paint;
import android.support.annotation.RestrictTo;
import android.support.v17.leanback.R;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -125,6 +126,11 @@
} else {
vh.mTitleView.setText(headerItem.getName());
if (vh.mDescriptionView != null) {
+ if (TextUtils.isEmpty(headerItem.getDescription())) {
+ vh.mDescriptionView.setVisibility(View.GONE);
+ } else {
+ vh.mDescriptionView.setVisibility(View.VISIBLE);
+ }
vh.mDescriptionView.setText(headerItem.getDescription());
}
viewHolder.view.setContentDescription(headerItem.getContentDescription());
diff --git a/v17/leanback/tests/AndroidManifest.xml b/v17/leanback/tests/AndroidManifest.xml
index 16b78cb..6e6b157 100644
--- a/v17/leanback/tests/AndroidManifest.xml
+++ b/v17/leanback/tests/AndroidManifest.xml
@@ -38,6 +38,10 @@
android:theme="@style/Theme.Leanback.GuidedStep"
android:exported="true" />
+ <activity android:name="android.support.v17.leanback.app.RowsFragmentTestActivity"
+ android:theme="@style/Theme.Leanback"
+ android:exported="true" />
+
<activity android:name="android.support.v17.leanback.app.BrowseFragmentTestActivity"
android:theme="@style/Theme.Leanback.Browse"
android:exported="true" />
@@ -46,6 +50,10 @@
android:theme="@style/Theme.Leanback"
android:exported="true" />
+ <activity android:name="android.support.v17.leanback.app.RowsSupportFragmentTestActivity"
+ android:theme="@style/Theme.Leanback"
+ android:exported="true" />
+
<activity android:name="android.support.v17.leanback.app.BrowseSupportFragmentTestActivity"
android:theme="@style/Theme.Leanback.Browse"
android:exported="true" />
@@ -58,6 +66,14 @@
android:theme="@style/Theme.Leanback.GuidedStep"
android:exported="true" />
+ <activity android:name="android.support.v17.leanback.app.PlaybackTestActivity"
+ android:theme="@style/Theme.Leanback"
+ android:exported="true" />
+
+ <activity android:name="android.support.v17.leanback.app.PlaybackSupportTestActivity"
+ android:theme="@style/Theme.Leanback"
+ android:exported="true" />
+
<activity android:name="android.support.v17.leanback.app.PlaybackOverlayTestActivity"
android:theme="@style/Theme.Leanback"
android:exported="true" />
diff --git a/v17/leanback/tests/generatev4.py b/v17/leanback/tests/generatev4.py
index 3e25503..ecae656 100755
--- a/v17/leanback/tests/generatev4.py
+++ b/v17/leanback/tests/generatev4.py
@@ -19,11 +19,11 @@
print "Generate v4 fragment related code for leanback"
-files = ['BrowseTest', 'GuidedStepTest']
+files = ['BrowseTest', 'GuidedStepTest', 'RowsTest']
cls = ['BrowseTest', 'Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid', 'Branded',
- 'GuidedStepTest', 'GuidedStep']
+ 'GuidedStepTest', 'GuidedStep', 'RowsTest']
for w in files:
print "copy {}Fragment to {}SupportFragment".format(w, w)
@@ -68,7 +68,7 @@
file.close()
outfile.close()
-testcls = ['Browse', 'GuidedStep', 'VerticalGrid']
+testcls = ['Browse', 'GuidedStep', 'VerticalGrid', 'Rows']
for w in testcls:
print "copy {}FrgamentTest to {}SupportFragmentTest".format(w, w)
@@ -95,7 +95,7 @@
file.close()
outfile.close()
-testcls = ['Browse', 'GuidedStep']
+testcls = ['Browse', 'GuidedStep', 'Rows']
for w in testcls:
print "copy {}FragmentTestActivity to {}SupportFragmentTestActivity".format(w, w)
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
index c6662ab..c24a7e5 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
@@ -16,6 +16,7 @@
package android.support.v17.leanback.app;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.content.Intent;
import android.graphics.Rect;
@@ -87,6 +88,9 @@
bitmapDrawable.getBounds().height());
assertEquals(0, bitmapDrawable.getVerticalOffset());
+ assertTrue("TitleView is visible", detailsFragment.getView()
+ .findViewById(R.id.browse_title_group).getVisibility() == View.VISIBLE);
+
activityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -97,7 +101,9 @@
PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
@Override
public boolean canProceed() {
- return bitmapDrawable.getVerticalOffset() == mDefaultVerticalOffset;
+ return bitmapDrawable.getVerticalOffset() == mDefaultVerticalOffset
+ && detailsFragment.getView()
+ .findViewById(R.id.browse_title_group).getVisibility() != View.VISIBLE;
}
});
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
index 192b20e..50aaa62 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
@@ -29,6 +29,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.support.v17.leanback.testutils.PollingCheck;
import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -354,4 +355,58 @@
PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
}
+
+ @Test
+ public void setActionsWhenSubActionsExpanded() throws Throwable {
+ final String firstFragmentName = generateMethodTestName("first");
+ GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+ doAnswer(new Answer<Void>() {
+ public Void answer(InvocationOnMock invocation) {
+ List actions = (List) invocation.getArguments()[0];
+ List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+ subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+ actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+ .title("list").build());
+ return null;
+ }
+ }).when(first).onCreateActions(any(List.class), any(Bundle.class));
+ doAnswer(new Answer<Boolean>() {
+ public Boolean answer(InvocationOnMock invocation) {
+ GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+ invocation.getMock();
+ GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+ if (action.getId() == 2000) {
+ List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+ newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+ obj.getFragment().setActions(newActions);
+ return false;
+ }
+ return false;
+ }
+ }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+ final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+ // after clicked, it sub actions list should expand
+ View firstView = first.getFragment().getActionItemView(0);
+ assertTrue(firstView.hasFocus());
+ sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+ PollingCheck.waitFor(new ExpandTransitionFinish(first));
+ assertFalse(firstView.hasFocus());
+
+ sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+ ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+ verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+ // after clicked a sub action, whole action list is replaced.
+ PollingCheck.waitFor(new ExpandTransitionFinish(first));
+ assertFalse(first.getFragment().isExpanded());
+ View newFirstView = first.getFragment().getActionItemView(0);
+ assertTrue(newFirstView.hasFocus());
+ assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+ GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+ .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+ assertEquals(1001, vh.getAction().getId());
+
+ }
+
}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
index 879292f..7d6b54f 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
@@ -32,6 +32,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.support.v17.leanback.testutils.PollingCheck;
import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -357,4 +358,58 @@
PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
}
+
+ @Test
+ public void setActionsWhenSubActionsExpanded() throws Throwable {
+ final String firstFragmentName = generateMethodTestName("first");
+ GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+ doAnswer(new Answer<Void>() {
+ public Void answer(InvocationOnMock invocation) {
+ List actions = (List) invocation.getArguments()[0];
+ List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+ subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+ actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+ .title("list").build());
+ return null;
+ }
+ }).when(first).onCreateActions(any(List.class), any(Bundle.class));
+ doAnswer(new Answer<Boolean>() {
+ public Boolean answer(InvocationOnMock invocation) {
+ GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+ invocation.getMock();
+ GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+ if (action.getId() == 2000) {
+ List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+ newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+ obj.getFragment().setActions(newActions);
+ return false;
+ }
+ return false;
+ }
+ }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+ final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+ // after clicked, it sub actions list should expand
+ View firstView = first.getFragment().getActionItemView(0);
+ assertTrue(firstView.hasFocus());
+ sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+ PollingCheck.waitFor(new ExpandTransitionFinish(first));
+ assertFalse(firstView.hasFocus());
+
+ sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+ ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+ verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+ // after clicked a sub action, whole action list is replaced.
+ PollingCheck.waitFor(new ExpandTransitionFinish(first));
+ assertFalse(first.getFragment().isExpanded());
+ View newFirstView = first.getFragment().getActionItemView(0);
+ assertTrue(newFirstView.hasFocus());
+ assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+ GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+ .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+ assertEquals(1001, vh.getAction().getId());
+
+ }
+
}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
index 45b8802..326d2be 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
@@ -19,20 +19,29 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.view.KeyEvent;
+import android.view.View;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@RunWith(AndroidJUnit4.class)
@MediumTest
@@ -41,6 +50,8 @@
static class PlayControlGlueImpl extends PlaybackControlGlue {
int mSpeedId = PLAYBACK_SPEED_PAUSED;
+ // number of times onRowChanged callback is called
+ int mOnRowChangedCallCount = 0;
PlayControlGlueImpl(Context context, int[] seekSpeeds) {
super(context, seekSpeeds);
@@ -50,6 +61,11 @@
super(context, ffSpeeds, rwSpeeds);
}
+ PlayControlGlueImpl(Context context, PlaybackOverlayFragment fragment,
+ int[] seekSpeeds) {
+ super(context, fragment, seekSpeeds);
+ }
+
@Override
public boolean hasValidMedia() {
return true;
@@ -115,6 +131,16 @@
@Override
protected void onRowChanged(PlaybackControlsRow row) {
+ mOnRowChangedCallCount++;
+ }
+
+ public void notifyMetaDataChanged() {
+ onMetadataChanged();
+ onStateChanged();
+ }
+
+ public int getOnRowChangedCallCount() {
+ return mOnRowChangedCallCount;
}
}
@@ -502,4 +528,78 @@
assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
}
+ @Test
+ public void testOnItemClickedListener() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ final PlaybackOverlayFragment[] fragmentResult = new PlaybackOverlayFragment[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ fragmentResult[0] = new PlaybackOverlayFragment();
+ }
+ });
+ PlaybackOverlayFragment fragment = fragmentResult[0];
+ glue.setHost(new PlaybackControlGlue.PlaybackGlueHostOld(fragment));
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+ OnItemViewClickedListener listener = Mockito.mock(OnItemViewClickedListener.class);
+ glue.setOnItemViewClickedListener(listener);
+
+ // create fake row ViewHolder and fade item ViewHolder
+ View rowView = new View(context);
+ View view = new View(context);
+ PlaybackRowPresenter.ViewHolder rowVh = new PlaybackRowPresenter.ViewHolder(rowView);
+ Presenter.ViewHolder vh = new Presenter.ViewHolder(view);
+
+ // Initially media is paused
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+
+ // simulate a click inside PlaybackOverlayFragment's PlaybackRow.
+ fragment.getOnItemViewClickedListener().onItemClicked(vh, playPause, rowVh, row);
+ verify(listener, times(0)).onItemClicked(vh, playPause, rowVh, row);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+
+ // simulate a click on object other than PlaybackRow.
+ Object regularItem = new Object();
+ Row regularRow = new Row();
+ RowPresenter.ViewHolder regularRowViewHolder = new RowPresenter.ViewHolder(rowView);
+ Presenter.ViewHolder regularViewHOlder = new Presenter.ViewHolder(view);
+ fragment.getOnItemViewClickedListener().onItemClicked(regularViewHOlder, regularItem,
+ regularRowViewHolder, regularRow);
+ verify(listener, times(1)).onItemClicked(regularViewHOlder, regularItem,
+ regularRowViewHolder, regularRow);
+ assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testOnRowChangedCallback() throws Exception {
+ final PlaybackOverlayFragment[] fragmentResult = new
+ PlaybackOverlayFragment[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ fragmentResult[0] = new PlaybackOverlayFragment();
+ }
+ });
+ PlaybackOverlayFragment fragment = fragmentResult[0];
+ PlayControlGlueImpl playbackGlue = new PlayControlGlueImpl(context, fragment,
+ new int[]{
+ PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
+ PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1,
+ PlaybackControlGlue.PLAYBACK_SPEED_FAST_L2
+ });
+
+ // before any controls row is created the count is zero
+ assertEquals(playbackGlue.getOnRowChangedCallCount(), 0);
+ playbackGlue.createControlsRowAndPresenter();
+ // after a controls row is created, onRowChanged() call back is called once
+ assertEquals(playbackGlue.getOnRowChangedCallCount(), 1);
+ playbackGlue.notifyMetaDataChanged();
+ // onMetaDataChanged() calls updateRowMetadata which ends up calling
+ // notifyPlaybackRowChanged on the old host and finally onRowChanged on the glue.
+ assertEquals(playbackGlue.getOnRowChangedCallCount(), 2);
+ }
}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java
index d3e3271..748a39f 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java
@@ -22,28 +22,39 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.view.KeyEvent;
+import android.view.View;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class PlaybackControlSupportGlueTest {
- static class PlayControlGlueImpl extends PlaybackControlSupportGlue {
+ public static class PlayControlGlueImpl extends PlaybackControlSupportGlue {
int mSpeedId = PLAYBACK_SPEED_PAUSED;
+ // number of times onRowChanged callback is called
+ int mOnRowChangedCallCount = 0;
PlayControlGlueImpl(Context context, int[] seekSpeeds) {
super(context, seekSpeeds);
@@ -53,6 +64,11 @@
super(context, ffSpeeds, rwSpeeds);
}
+ PlayControlGlueImpl(Context context, PlaybackOverlaySupportFragment fragment,
+ int[] seekSpeeds) {
+ super(context, fragment, seekSpeeds);
+ }
+
@Override
public boolean hasValidMedia() {
return true;
@@ -118,6 +134,16 @@
@Override
protected void onRowChanged(PlaybackControlsRow row) {
+ mOnRowChangedCallCount++;
+ }
+
+ public void notifyMetaDataChanged() {
+ onMetadataChanged();
+ onStateChanged();
+ }
+
+ public int getOnRowChangedCallCount() {
+ return mOnRowChangedCallCount;
}
}
@@ -505,4 +531,79 @@
assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
}
+ @Test
+ public void testOnItemClickedListener() {
+ PlaybackControlsRow row = new PlaybackControlsRow();
+ final PlaybackOverlayFragment[] fragmentResult = new PlaybackOverlayFragment[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ fragmentResult[0] = new PlaybackOverlayFragment();
+ }
+ });
+ PlaybackOverlayFragment fragment = fragmentResult[0];
+ glue.setHost(new PlaybackControlSupportGlue.PlaybackGlueHostOld(fragment));
+ glue.setControlsRow(row);
+ SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+ row.getPrimaryActionsAdapter();
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+ .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE);
+ OnItemViewClickedListener listener = Mockito.mock(OnItemViewClickedListener.class);
+ glue.setOnItemViewClickedListener(listener);
+
+ // create fake row ViewHolder and fade item ViewHolder
+ View rowView = new View(context);
+ View view = new View(context);
+ PlaybackRowPresenter.ViewHolder rowVh = new PlaybackRowPresenter.ViewHolder(rowView);
+ Presenter.ViewHolder vh = new Presenter.ViewHolder(view);
+
+ // Initially media is paused
+ assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+
+ // simulate a click inside PlaybackOverlayFragment's PlaybackRow.
+ fragment.getOnItemViewClickedListener().onItemClicked(vh, playPause, rowVh, row);
+ verify(listener, times(0)).onItemClicked(vh, playPause, rowVh, row);
+ assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+
+ // simulate a click on object other than PlaybackRow.
+ Object regularItem = new Object();
+ Row regularRow = new Row();
+ RowPresenter.ViewHolder regularRowViewHolder = new RowPresenter.ViewHolder(rowView);
+ Presenter.ViewHolder regularViewHOlder = new Presenter.ViewHolder(view);
+ fragment.getOnItemViewClickedListener().onItemClicked(regularViewHOlder, regularItem,
+ regularRowViewHolder, regularRow);
+ verify(listener, times(1)).onItemClicked(regularViewHOlder, regularItem,
+ regularRowViewHolder, regularRow);
+ assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+ }
+
+ @Test
+ public void testOnRowChangedCallback() throws Exception {
+ final PlaybackOverlaySupportFragment[] fragmentResult = new
+ PlaybackOverlaySupportFragment[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ fragmentResult[0] = new PlaybackOverlaySupportFragment();
+ }
+ });
+ PlaybackOverlaySupportFragment fragment = fragmentResult[0];
+ PlayControlGlueImpl playbackGlue = new PlayControlGlueImpl(context, fragment,
+ new int[]{
+ PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0,
+ PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L1,
+ PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L2
+ });
+
+ // before any controls row is created the count is zero
+ assertEquals(playbackGlue.getOnRowChangedCallCount(), 0);
+ playbackGlue.createControlsRowAndPresenter();
+ // after a controls row is created, onRowChanged() call back is called once
+ assertEquals(playbackGlue.getOnRowChangedCallCount(), 1);
+ playbackGlue.notifyMetaDataChanged();
+ // onMetaDataChanged() calls updateRowMetadata which ends up calling
+ // notifyPlaybackRowChanged on the old host and finally onRowChanged on the glue.
+ assertEquals(playbackGlue.getOnRowChangedCallCount(), 2);
+ }
+
}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
new file mode 100644
index 0000000..bf7077b
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PlaybackFragmentTest {
+
+ private static final String TAG = "PlaybackFragmentTest";
+ private static final long TRANSITION_LENGTH = 1000;
+
+ @Rule
+ public ActivityTestRule<PlaybackTestActivity> activityTestRule =
+ new ActivityTestRule<>(PlaybackTestActivity.class, false, false);
+ private PlaybackTestActivity mActivity;
+
+ @Test
+ public void testSelectedListener() throws Throwable {
+ Intent intent = new Intent();
+ mActivity = activityTestRule.launchActivity(intent);
+ PlaybackTestFragment fragment = mActivity.getPlaybackFragment();
+ assertTrue(fragment.getView().hasFocus());
+
+ OnItemViewSelectedListener selectedListener = Mockito.mock(
+ OnItemViewSelectedListener.class);
+ fragment.setOnItemViewSelectedListener(selectedListener);
+
+
+ PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+ SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+ controlsRow.getPrimaryActionsAdapter();
+
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+ PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+ ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+ ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+ ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+ ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+ ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+ ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+ // First navigate left within PlaybackControlsRow items.
+ verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+ // Now navigate down to a ListRow item.
+ ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same list row should be passed to the listener", listRow0,
+ rowCaptor.getValue());
+ // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
+ // selected.
+ boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+ || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+ assertTrue("None of the items in the first ListRow are passed to the selected listener.",
+ listRowItemPassed);
+ }
+
+ @Test
+ public void testClickedListener() throws Throwable {
+ Intent intent = new Intent();
+ mActivity = activityTestRule.launchActivity(intent);
+ PlaybackTestFragment fragment = mActivity.getPlaybackFragment();
+ assertTrue(fragment.getView().hasFocus());
+
+ OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
+ fragment.setOnItemViewClickedListener(clickedListener);
+
+
+ PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+ SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+ controlsRow.getPrimaryActionsAdapter();
+
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+ PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+ ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+ ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+ ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+ ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+ ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+ ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+ // First navigate left within PlaybackControlsRow items.
+ verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+ // Now navigate down to a ListRow item.
+ ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same list row should be passed to the listener", listRow0,
+ rowCaptor.getValue());
+ boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+ || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+ assertTrue("None of the items in the first ListRow are passed to the click listener.",
+ listRowItemPassed);
+ }
+
+ private void sendKeys(int ...keys) {
+ for (int i = 0; i < keys.length; i++) {
+ InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+ }
+ }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
new file mode 100644
index 0000000..fdba125
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PlaybackSupportFragmentTest {
+
+ private static final String TAG = "PlaybackSupportFragmentTest";
+ private static final long TRANSITION_LENGTH = 1000;
+
+ @Rule
+ public ActivityTestRule<PlaybackSupportTestActivity> activityTestRule =
+ new ActivityTestRule<>(PlaybackSupportTestActivity.class, false, false);
+ private PlaybackSupportTestActivity mActivity;
+
+ @Test
+ public void testSelectedListener() throws Throwable {
+ Intent intent = new Intent();
+ mActivity = activityTestRule.launchActivity(intent);
+ PlaybackSupportTestFragment fragment = mActivity.getPlaybackFragment();
+ assertTrue(fragment.getView().hasFocus());
+
+ OnItemViewSelectedListener selectedListener = Mockito.mock(
+ OnItemViewSelectedListener.class);
+ fragment.setOnItemViewSelectedListener(selectedListener);
+
+
+ PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+ SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+ controlsRow.getPrimaryActionsAdapter();
+
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+ PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+ ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+ ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+ ArgumentCaptor<Object> itemCaptor =
+ ArgumentCaptor.forClass(Object.class);
+ ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+ ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+ ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+ // First navigate left within PlaybackControlsRow items.
+ verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+ // Now navigate down to a ListRow item.
+ ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same list row should be passed to the listener", listRow0,
+ rowCaptor.getValue());
+ // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
+ // selected.
+ boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+ || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+ assertTrue("None of the items in the first ListRow are passed to the selected listener.",
+ listRowItemPassed);
+ }
+
+ @Test
+ public void testClickedListener() throws Throwable {
+ Intent intent = new Intent();
+ mActivity = activityTestRule.launchActivity(intent);
+ PlaybackSupportTestFragment fragment = mActivity.getPlaybackFragment();
+ assertTrue(fragment.getView().hasFocus());
+
+ OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
+ fragment.setOnItemViewClickedListener(clickedListener);
+
+
+ PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+ SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+ controlsRow.getPrimaryActionsAdapter();
+
+ PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+ PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+ PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+ primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+ ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+ ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+ ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+ ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+ ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+ ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+ // First navigate left within PlaybackControlsRow items.
+ verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same controls row should be passed to the listener", controlsRow,
+ rowCaptor.getValue());
+ assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+ // Now navigate down to a ListRow item.
+ ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
+ any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
+ itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+ assertSame("Same list row should be passed to the listener", listRow0,
+ rowCaptor.getValue());
+ boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+ || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+ assertTrue("None of the items in the first ListRow are passed to the click listener.",
+ listRowItemPassed);
+ }
+
+ private void sendKeys(int ...keys) {
+ for (int i = 0; i < keys.length; i++) {
+ InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+ }
+ }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestActivity.java
new file mode 100644
index 0000000..c85fe83
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlaybackSupportTestActivity extends FragmentActivity {
+ private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.playback_support_controls);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ for (PictureInPictureListener listener : mListeners) {
+ listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+
+ public void registerPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public interface PictureInPictureListener {
+ void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+ }
+
+ public PlaybackSupportTestFragment getPlaybackFragment() {
+ return (PlaybackSupportTestFragment) getSupportFragmentManager().findFragmentById(
+ R.id.playback_controls_fragment);
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestFragment.java
new file mode 100644
index 0000000..4a07a60
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestFragment.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+public class PlaybackSupportTestFragment extends PlaybackSupportFragment {
+ private static final String TAG = "PlaybackTestFragment";
+
+ /**
+ * Change this to choose a different overlay background.
+ */
+ private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+ private static final int ROW_CONTROLS = 0;
+
+ /**
+ * Change this to select hidden
+ */
+ private static final boolean SECONDARY_HIDDEN = false;
+
+ /**
+ * Change the number of related content rows.
+ */
+ private static final int RELATED_CONTENT_ROWS = 3;
+
+ private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
+ private ListRowPresenter mListRowPresenter;
+
+ public SparseArrayObjectAdapter getAdapter() {
+ return (SparseArrayObjectAdapter) super.getAdapter();
+ }
+
+ private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.d(TAG, "onItemClicked: " + item + " row " + row);
+ }
+ };
+
+ private OnItemViewSelectedListener mOnItemViewSelectedListener =
+ new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.d(TAG, "onItemSelected: " + item + " row " + row);
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ setBackgroundType(BACKGROUND_TYPE);
+ // setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+
+ createComponents(getActivity());
+ setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+
+ private void createComponents(Context context) {
+ mGlue = new PlaybackControlHelper(context) {
+ @Override
+ public int getUpdatePeriod() {
+ int totalTime = getControlsRow().getTotalTime();
+ if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+ return 1000;
+ }
+ return Math.max(16, totalTime / getView().getWidth());
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == R.id.lb_control_picture_in_picture) {
+ getActivity().enterPictureInPictureMode();
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ protected void onCreateControlsRowAndPresenter() {
+ super.onCreateControlsRowAndPresenter();
+ getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+ }
+ };
+
+ mGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
+ // mGlue.setOnI
+ mListRowPresenter = new ListRowPresenter();
+
+ setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+ @Override
+ public Presenter getPresenter(Object object) {
+ if (object instanceof PlaybackControlsRow) {
+ return mGlue.getControlsRowPresenter();
+ } else if (object instanceof ListRow) {
+ return mListRowPresenter;
+ }
+ throw new IllegalArgumentException("Unhandled object: " + object);
+ }
+ }));
+
+ // Add the controls row
+ getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+ // Add related content rows
+ for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+ listRowAdapter.add("Some related content");
+ listRowAdapter.add("Other related content");
+ HeaderItem header = new HeaderItem(i, "Row " + i);
+ getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+ }
+ }
+
+ public PlaybackControlGlue getGlue() {
+ return mGlue;
+ }
+
+ abstract static class PlaybackControlHelper extends PlaybackControlGlue {
+ /**
+ * Change the location of the thumbs up/down controls
+ */
+ private static final boolean THUMBS_PRIMARY = true;
+
+ private static final String FAUX_TITLE = "A short song of silence";
+ private static final String FAUX_SUBTITLE = "2014";
+ private static final int FAUX_DURATION = 33 * 1000;
+
+ // These should match the playback service FF behavior
+ private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+ private boolean mIsPlaying;
+ private int mSpeed = PLAYBACK_SPEED_PAUSED;
+ private long mStartTime;
+ private long mStartPosition = 0;
+
+ private PlaybackControlsRow.RepeatAction mRepeatAction;
+ private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+ private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+ private PlaybackControlsRow.PictureInPictureAction mPipAction;
+ private static Handler sProgressHandler = new Handler();
+
+ private final Runnable mUpdateProgressRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateProgress();
+ sProgressHandler.postDelayed(this, getUpdatePeriod());
+ }
+ };
+
+ PlaybackControlHelper(Context context) {
+ super(context, sFastForwardSpeeds);
+ mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+ mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+ mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+ mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+ }
+
+ @Override
+ protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+ PresenterSelector presenterSelector) {
+ SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+ if (THUMBS_PRIMARY) {
+ adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+ adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+ }
+ return adapter;
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return true;
+ }
+ }
+ return super.onKey(view, keyCode, keyEvent);
+ }
+
+ private boolean shouldDispatchAction(Action action) {
+ return action == mRepeatAction || action == mThumbsUpAction
+ || action == mThumbsDownAction;
+ }
+
+ private void dispatchAction(Action action) {
+ Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+ PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+ multiAction.nextIndex();
+ notifyActionChanged(multiAction);
+ }
+
+ private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+ int index;
+ index = getPrimaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ } else {
+ index = getSecondaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ }
+ }
+ }
+
+ private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+ return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+ }
+
+ private ArrayObjectAdapter getSecondaryActionsAdapter() {
+ return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+ }
+
+ @Override
+ public boolean hasValidMedia() {
+ return true;
+ }
+
+ @Override
+ public boolean isMediaPlaying() {
+ return mIsPlaying;
+ }
+
+ @Override
+ public CharSequence getMediaTitle() {
+ return FAUX_TITLE;
+ }
+
+ @Override
+ public CharSequence getMediaSubtitle() {
+ return FAUX_SUBTITLE;
+ }
+
+ @Override
+ public int getMediaDuration() {
+ return FAUX_DURATION;
+ }
+
+ @Override
+ public Drawable getMediaArt() {
+ return null;
+ }
+
+ @Override
+ public long getSupportedActions() {
+ return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
+ }
+
+ @Override
+ public int getCurrentSpeedId() {
+ return mSpeed;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ int speed;
+ if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+ speed = 0;
+ } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+ speed = 1;
+ } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = getFastForwardSpeeds()[index];
+ } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = -getRewindSpeeds()[index];
+ } else {
+ return -1;
+ }
+ long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+ if (position > getMediaDuration()) {
+ position = getMediaDuration();
+ onPlaybackComplete(true);
+ } else if (position < 0) {
+ position = 0;
+ onPlaybackComplete(false);
+ }
+ return (int) position;
+ }
+
+ void onPlaybackComplete(final boolean ended) {
+ sProgressHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+ pause();
+ } else {
+ play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+ }
+ mStartPosition = 0;
+ onStateChanged();
+ }
+ });
+ }
+
+ @Override
+ public void play(int speed) {
+ if (speed == mSpeed) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = speed;
+ mIsPlaying = true;
+ mStartTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void pause() {
+ if (mSpeed == PLAYBACK_SPEED_PAUSED) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = PLAYBACK_SPEED_PAUSED;
+ mIsPlaying = false;
+ }
+
+ @Override
+ public void next() {
+ // Not supported
+ }
+
+ @Override
+ public void previous() {
+ // Not supported
+ }
+
+ @Override
+ public void enableProgressUpdating(boolean enable) {
+ sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
+ if (enable) {
+ mUpdateProgressRunnable.run();
+ }
+ }
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestActivity.java
new file mode 100644
index 0000000..ff840ec
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlaybackTestActivity extends Activity {
+ private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.playback_controls);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ for (PictureInPictureListener listener : mListeners) {
+ listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+
+ public void registerPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public interface PictureInPictureListener {
+ void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+ }
+
+ public PlaybackTestFragment getPlaybackFragment() {
+ return (PlaybackTestFragment) getFragmentManager().findFragmentById(
+ R.id.playback_controls_fragment);
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
new file mode 100644
index 0000000..043d73e
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+public class PlaybackTestFragment extends PlaybackFragment {
+ private static final String TAG = "PlaybackTestFragment";
+
+ /**
+ * Change this to choose a different overlay background.
+ */
+ private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+ private static final int ROW_CONTROLS = 0;
+
+ /**
+ * Change this to select hidden
+ */
+ private static final boolean SECONDARY_HIDDEN = false;
+
+ /**
+ * Change the number of related content rows.
+ */
+ private static final int RELATED_CONTENT_ROWS = 3;
+
+ private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
+ private ListRowPresenter mListRowPresenter;
+
+ public SparseArrayObjectAdapter getAdapter() {
+ return (SparseArrayObjectAdapter) super.getAdapter();
+ }
+
+ private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.d(TAG, "onItemClicked: " + item + " row " + row);
+ }
+ };
+
+ private OnItemViewSelectedListener mOnItemViewSelectedListener =
+ new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.d(TAG, "onItemSelected: " + item + " row " + row);
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ setBackgroundType(BACKGROUND_TYPE);
+ // setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+
+ createComponents(getActivity());
+ setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+
+ private void createComponents(Context context) {
+ mGlue = new PlaybackControlHelper(context) {
+ @Override
+ public int getUpdatePeriod() {
+ int totalTime = getControlsRow().getTotalTime();
+ if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+ return 1000;
+ }
+ return Math.max(16, totalTime / getView().getWidth());
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (action.getId() == R.id.lb_control_picture_in_picture) {
+ getActivity().enterPictureInPictureMode();
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ protected void onCreateControlsRowAndPresenter() {
+ super.onCreateControlsRowAndPresenter();
+ getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+ }
+ };
+
+ mGlue.setHost(new PlaybackFragmentGlueHost(this));
+ // mGlue.setOnI
+ mListRowPresenter = new ListRowPresenter();
+
+ setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+ @Override
+ public Presenter getPresenter(Object object) {
+ if (object instanceof PlaybackControlsRow) {
+ return mGlue.getControlsRowPresenter();
+ } else if (object instanceof ListRow) {
+ return mListRowPresenter;
+ }
+ throw new IllegalArgumentException("Unhandled object: " + object);
+ }
+ }));
+
+ // Add the controls row
+ getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+ // Add related content rows
+ for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+ listRowAdapter.add("Some related content");
+ listRowAdapter.add("Other related content");
+ HeaderItem header = new HeaderItem(i, "Row " + i);
+ getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+ }
+ }
+
+ public PlaybackControlGlue getGlue() {
+ return mGlue;
+ }
+
+ abstract static class PlaybackControlHelper extends PlaybackControlGlue {
+ /**
+ * Change the location of the thumbs up/down controls
+ */
+ private static final boolean THUMBS_PRIMARY = true;
+
+ private static final String FAUX_TITLE = "A short song of silence";
+ private static final String FAUX_SUBTITLE = "2014";
+ private static final int FAUX_DURATION = 33 * 1000;
+
+ // These should match the playback service FF behavior
+ private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+ private boolean mIsPlaying;
+ private int mSpeed = PLAYBACK_SPEED_PAUSED;
+ private long mStartTime;
+ private long mStartPosition = 0;
+
+ private PlaybackControlsRow.RepeatAction mRepeatAction;
+ private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+ private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+ private PlaybackControlsRow.PictureInPictureAction mPipAction;
+ private static Handler sProgressHandler = new Handler();
+
+ private final Runnable mUpdateProgressRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateProgress();
+ sProgressHandler.postDelayed(this, getUpdatePeriod());
+ }
+ };
+
+ PlaybackControlHelper(Context context) {
+ super(context, sFastForwardSpeeds);
+ mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+ mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+ mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+ mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+ }
+
+ @Override
+ protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+ PresenterSelector presenterSelector) {
+ SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+ if (THUMBS_PRIMARY) {
+ adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+ adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+ }
+ return adapter;
+ }
+
+ @Override
+ public void onActionClicked(Action action) {
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return;
+ }
+ super.onActionClicked(action);
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+ if (shouldDispatchAction(action)) {
+ dispatchAction(action);
+ return true;
+ }
+ }
+ return super.onKey(view, keyCode, keyEvent);
+ }
+
+ private boolean shouldDispatchAction(Action action) {
+ return action == mRepeatAction || action == mThumbsUpAction
+ || action == mThumbsDownAction;
+ }
+
+ private void dispatchAction(Action action) {
+ Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+ PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+ multiAction.nextIndex();
+ notifyActionChanged(multiAction);
+ }
+
+ private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+ int index;
+ index = getPrimaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ } else {
+ index = getSecondaryActionsAdapter().indexOf(action);
+ if (index >= 0) {
+ getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+ }
+ }
+ }
+
+ private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+ return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+ }
+
+ private ArrayObjectAdapter getSecondaryActionsAdapter() {
+ return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+ }
+
+ @Override
+ public boolean hasValidMedia() {
+ return true;
+ }
+
+ @Override
+ public boolean isMediaPlaying() {
+ return mIsPlaying;
+ }
+
+ @Override
+ public CharSequence getMediaTitle() {
+ return FAUX_TITLE;
+ }
+
+ @Override
+ public CharSequence getMediaSubtitle() {
+ return FAUX_SUBTITLE;
+ }
+
+ @Override
+ public int getMediaDuration() {
+ return FAUX_DURATION;
+ }
+
+ @Override
+ public Drawable getMediaArt() {
+ return null;
+ }
+
+ @Override
+ public long getSupportedActions() {
+ return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
+ }
+
+ @Override
+ public int getCurrentSpeedId() {
+ return mSpeed;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ int speed;
+ if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+ speed = 0;
+ } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+ speed = 1;
+ } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = getFastForwardSpeeds()[index];
+ } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+ int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+ speed = -getRewindSpeeds()[index];
+ } else {
+ return -1;
+ }
+ long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+ if (position > getMediaDuration()) {
+ position = getMediaDuration();
+ onPlaybackComplete(true);
+ } else if (position < 0) {
+ position = 0;
+ onPlaybackComplete(false);
+ }
+ return (int) position;
+ }
+
+ void onPlaybackComplete(final boolean ended) {
+ sProgressHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+ pause();
+ } else {
+ play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+ }
+ mStartPosition = 0;
+ onStateChanged();
+ }
+ });
+ }
+
+ @Override
+ public void play(int speed) {
+ if (speed == mSpeed) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = speed;
+ mIsPlaying = true;
+ mStartTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void pause() {
+ if (mSpeed == PLAYBACK_SPEED_PAUSED) {
+ return;
+ }
+ mStartPosition = getCurrentPosition();
+ mSpeed = PLAYBACK_SPEED_PAUSED;
+ mIsPlaying = false;
+ }
+
+ @Override
+ public void next() {
+ // Not supported
+ }
+
+ @Override
+ public void previous() {
+ // Not supported
+ }
+
+ @Override
+ public void enableProgressUpdating(boolean enable) {
+ sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
+ if (enable) {
+ mUpdateProgressRunnable.run();
+ }
+ }
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
new file mode 100644
index 0000000..50d5f24
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RowsFragmentTest {
+
+ static final long ACTIVITY_LOAD_DELAY = 2000;
+
+ @Rule
+ public ActivityTestRule<RowsFragmentTestActivity> activityTestRule =
+ new ActivityTestRule<>(RowsFragmentTestActivity.class, false, false);
+ private RowsFragmentTestActivity mActivity;
+
+ @After
+ public void afterTest() throws Throwable {
+ activityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ if (mActivity != null) {
+ mActivity.finish();
+ mActivity = null;
+ }
+ }
+ });
+ }
+
+ private void sendKeys(int ...keys) {
+ for (int i = 0; i < keys.length; i++) {
+ InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+ }
+ }
+
+ void launchAndWaitActivity(Intent intent) {
+ mActivity = activityTestRule.launchActivity(intent);
+ SystemClock.sleep(ACTIVITY_LOAD_DELAY);
+ }
+
+ @Test
+ public void defaultAlignment() throws InterruptedException {
+ Intent intent = new Intent();
+ intent.putExtra(RowsFragmentTestActivity.EXTRA_NUM_ROWS, 10);
+ intent.putExtra(RowsFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, 1l);
+ launchAndWaitActivity(intent);
+
+ final Rect rect = new Rect();
+
+ final VerticalGridView gridView = mActivity.getRowsTestFragment().getVerticalGridView();
+ View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
+ rect.set(0, 0, row0.getWidth(), row0.getHeight());
+ gridView.offsetDescendantRectToMyCoords(row0, rect);
+ assertEquals("First row is initially aligned to top of screen", 0, rect.top);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
+ PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
+
+ rect.set(0, 0, row1.getWidth(), row1.getHeight());
+ gridView.offsetDescendantRectToMyCoords(row1, rect);
+ assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
+ }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTestActivity.java
new file mode 100644
index 0000000..fe2dd25
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+
+public class RowsFragmentTestActivity extends Activity {
+
+ public static final String EXTRA_NUM_ROWS = "numRows";
+ public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
+ public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
+ public final static String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+
+ setContentView(R.layout.rows);
+ if (savedInstanceState == null) {
+ RowsTestFragment fragment = new RowsTestFragment();
+ Bundle arguments = new Bundle();
+ if (intent.getExtras() != null) {
+ arguments.putAll(intent.getExtras());
+ }
+ fragment.setArguments(arguments);
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(R.id.main_frame, fragment);
+ ft.commit();
+ }
+ }
+
+ public RowsTestFragment getRowsTestFragment() {
+ return (RowsTestFragment) getFragmentManager().findFragmentById(R.id.main_frame);
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
new file mode 100644
index 0000000..c024b6c
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
@@ -0,0 +1,99 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsFragmentTest.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RowsSupportFragmentTest {
+
+ static final long ACTIVITY_LOAD_DELAY = 2000;
+
+ @Rule
+ public ActivityTestRule<RowsSupportFragmentTestActivity> activityTestRule =
+ new ActivityTestRule<>(RowsSupportFragmentTestActivity.class, false, false);
+ private RowsSupportFragmentTestActivity mActivity;
+
+ @After
+ public void afterTest() throws Throwable {
+ activityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ if (mActivity != null) {
+ mActivity.finish();
+ mActivity = null;
+ }
+ }
+ });
+ }
+
+ private void sendKeys(int ...keys) {
+ for (int i = 0; i < keys.length; i++) {
+ InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+ }
+ }
+
+ void launchAndWaitActivity(Intent intent) {
+ mActivity = activityTestRule.launchActivity(intent);
+ SystemClock.sleep(ACTIVITY_LOAD_DELAY);
+ }
+
+ @Test
+ public void defaultAlignment() throws InterruptedException {
+ Intent intent = new Intent();
+ intent.putExtra(RowsSupportFragmentTestActivity.EXTRA_NUM_ROWS, 10);
+ intent.putExtra(RowsSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, 1l);
+ launchAndWaitActivity(intent);
+
+ final Rect rect = new Rect();
+
+ final VerticalGridView gridView = mActivity.getRowsTestSupportFragment().getVerticalGridView();
+ View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
+ rect.set(0, 0, row0.getWidth(), row0.getHeight());
+ gridView.offsetDescendantRectToMyCoords(row0, rect);
+ assertEquals("First row is initially aligned to top of screen", 0, rect.top);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
+ PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
+
+ rect.set(0, 0, row1.getWidth(), row1.getHeight());
+ gridView.offsetDescendantRectToMyCoords(row1, rect);
+ assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
+ }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTestActivity.java
new file mode 100644
index 0000000..d736458
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTestActivity.java
@@ -0,0 +1,56 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsFragmentTestActivity.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+
+public class RowsSupportFragmentTestActivity extends FragmentActivity {
+
+ public static final String EXTRA_NUM_ROWS = "numRows";
+ public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
+ public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
+ public final static String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+
+ setContentView(R.layout.rows);
+ if (savedInstanceState == null) {
+ RowsTestSupportFragment fragment = new RowsTestSupportFragment();
+ Bundle arguments = new Bundle();
+ if (intent.getExtras() != null) {
+ arguments.putAll(intent.getExtras());
+ }
+ fragment.setArguments(arguments);
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ ft.replace(R.id.main_frame, fragment);
+ ft.commit();
+ }
+ }
+
+ public RowsTestSupportFragment getRowsTestSupportFragment() {
+ return (RowsTestSupportFragment) getSupportFragmentManager().findFragmentById(R.id.main_frame);
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestFragment.java
new file mode 100644
index 0000000..d1f71db
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestFragment.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
+
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_NUM_ROWS;
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
+
+public class RowsTestFragment extends RowsFragment {
+ private static final String TAG = "RowsTestFragment";
+
+ final static int DEFAULT_NUM_ROWS = 100;
+ final static int DEFAULT_REPEAT_PER_ROW = 20;
+ final static long DEFAULT_LOAD_DATA_DELAY = 2000;
+ final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
+
+ private ArrayObjectAdapter mRowsAdapter;
+
+ // For good performance, it's important to use a single instance of
+ // a card presenter for all rows using that presenter.
+ final static StringPresenter sCardPresenter = new StringPresenter();
+
+ int NUM_ROWS;
+ int REPEAT_PER_ROW;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ Bundle arguments = getArguments();
+ NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, RowsTestFragment.DEFAULT_NUM_ROWS);
+ REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
+ DEFAULT_REPEAT_PER_ROW);
+ long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
+ DEFAULT_LOAD_DATA_DELAY);
+ final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
+ EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
+ DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
+
+ if (!SET_ADAPTER_AFTER_DATA_LOAD) {
+ setupRows();
+ }
+
+ setOnItemViewClickedListener(new ItemViewClickedListener());
+ setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
+ + " " + rowViewHolder
+ + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
+ }
+ });
+ // simulates in a real world use case data being loaded two seconds later
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (getActivity() == null || getActivity().isDestroyed()) {
+ return;
+ }
+ if (SET_ADAPTER_AFTER_DATA_LOAD) {
+ setupRows();
+ }
+ loadData();
+ }
+ }, LOAD_DATA_DELAY);
+ }
+
+ private void setupRows() {
+ ListRowPresenter lrp = new ListRowPresenter();
+
+ mRowsAdapter = new ArrayObjectAdapter(lrp);
+
+ setAdapter(mRowsAdapter);
+ }
+
+ private void loadData() {
+ for (int i = 0; i < NUM_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+ int index = 0;
+ for (int j = 0; j < REPEAT_PER_ROW; ++j) {
+ listRowAdapter.add("Hello world-" + (index++));
+ listRowAdapter.add("This is a test-" + (index++));
+ listRowAdapter.add("Android TV-" + (index++));
+ listRowAdapter.add("Leanback-" + (index++));
+ listRowAdapter.add("Hello world-" + (index++));
+ listRowAdapter.add("Android TV-" + (index++));
+ listRowAdapter.add("Leanback-" + (index++));
+ listRowAdapter.add("GuidedStepFragment-" + (index++));
+ }
+ HeaderItem header = new HeaderItem(i, "Row " + i);
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+ }
+ }
+
+ private final class ItemViewClickedListener implements OnItemViewClickedListener {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemClicked: " + item + " row " + row);
+ }
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestSupportFragment.java
new file mode 100644
index 0000000..e095f94
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestSupportFragment.java
@@ -0,0 +1,132 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsTestFragment.java. DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
+
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_NUM_ROWS;
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
+
+public class RowsTestSupportFragment extends RowsSupportFragment {
+ private static final String TAG = "RowsTestSupportFragment";
+
+ final static int DEFAULT_NUM_ROWS = 100;
+ final static int DEFAULT_REPEAT_PER_ROW = 20;
+ final static long DEFAULT_LOAD_DATA_DELAY = 2000;
+ final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
+
+ private ArrayObjectAdapter mRowsAdapter;
+
+ // For good performance, it's important to use a single instance of
+ // a card presenter for all rows using that presenter.
+ final static StringPresenter sCardPresenter = new StringPresenter();
+
+ int NUM_ROWS;
+ int REPEAT_PER_ROW;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ Bundle arguments = getArguments();
+ NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, RowsTestSupportFragment.DEFAULT_NUM_ROWS);
+ REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
+ DEFAULT_REPEAT_PER_ROW);
+ long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
+ DEFAULT_LOAD_DATA_DELAY);
+ final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
+ EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
+ DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
+
+ if (!SET_ADAPTER_AFTER_DATA_LOAD) {
+ setupRows();
+ }
+
+ setOnItemViewClickedListener(new ItemViewClickedListener());
+ setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+ @Override
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
+ + " " + rowViewHolder
+ + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
+ }
+ });
+ // simulates in a real world use case data being loaded two seconds later
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (getActivity() == null || getActivity().isDestroyed()) {
+ return;
+ }
+ if (SET_ADAPTER_AFTER_DATA_LOAD) {
+ setupRows();
+ }
+ loadData();
+ }
+ }, LOAD_DATA_DELAY);
+ }
+
+ private void setupRows() {
+ ListRowPresenter lrp = new ListRowPresenter();
+
+ mRowsAdapter = new ArrayObjectAdapter(lrp);
+
+ setAdapter(mRowsAdapter);
+ }
+
+ private void loadData() {
+ for (int i = 0; i < NUM_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+ int index = 0;
+ for (int j = 0; j < REPEAT_PER_ROW; ++j) {
+ listRowAdapter.add("Hello world-" + (index++));
+ listRowAdapter.add("This is a test-" + (index++));
+ listRowAdapter.add("Android TV-" + (index++));
+ listRowAdapter.add("Leanback-" + (index++));
+ listRowAdapter.add("Hello world-" + (index++));
+ listRowAdapter.add("Android TV-" + (index++));
+ listRowAdapter.add("Leanback-" + (index++));
+ listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
+ }
+ HeaderItem header = new HeaderItem(i, "Row " + i);
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+ }
+ }
+
+ private final class ItemViewClickedListener implements OnItemViewClickedListener {
+ @Override
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ Log.i(TAG, "onItemClicked: " + item + " row " + row);
+ }
+ }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
index 8923d36..738c3e7 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
@@ -107,6 +107,30 @@
}
@Test
+ public void testRowHeaderPresenter() {
+ RowHeaderPresenter p = new RowHeaderPresenter();
+ p.setNullItemVisibilityGone(true);
+ RowHeaderPresenter.ViewHolder vh = (RowHeaderPresenter.ViewHolder)
+ p.onCreateViewHolder(new FrameLayout(mContext));
+ p.onBindViewHolder(vh, null);
+ assertEquals("Header visibility", View.GONE, vh.view.getVisibility());
+ p.onBindViewHolder(vh, new Row(null));
+ assertEquals("Header visibility", View.GONE, vh.view.getVisibility());
+
+ p.onBindViewHolder(vh, new Row(new HeaderItem("")));
+ assertEquals("Header visibility", View.VISIBLE, vh.view.getVisibility());
+ assertEquals("Header Description visibility", View.GONE,
+ vh.mDescriptionView.getVisibility());
+
+ HeaderItem item = new HeaderItem("");
+ item.setDescription("description");
+ p.onBindViewHolder(vh, new Row(item));
+ assertEquals("Header visibility", View.VISIBLE, vh.view.getVisibility());
+ assertEquals("Header Description visibility", View.VISIBLE,
+ vh.mDescriptionView.getVisibility());
+ }
+
+ @Test
public void testPlaybackControlsRowPresenter() {
Context context = new ContextThemeWrapper(mContext, R.style.Theme_Leanback);
Presenter detailsPresenter = new AbstractDetailsDescriptionPresenter() {
diff --git a/v17/leanback/tests/res/layout/playback_controls.xml b/v17/leanback/tests/res/layout/playback_controls.xml
new file mode 100644
index 0000000..7f8910f
--- /dev/null
+++ b/v17/leanback/tests/res/layout/playback_controls.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <fragment
+ android:id="@+id/playback_controls_fragment"
+ android:name="android.support.v17.leanback.app.PlaybackTestFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/tests/res/layout/playback_support_controls.xml b/v17/leanback/tests/res/layout/playback_support_controls.xml
new file mode 100644
index 0000000..9e0e092
--- /dev/null
+++ b/v17/leanback/tests/res/layout/playback_support_controls.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <fragment
+ android:id="@+id/playback_controls_fragment"
+ android:name="android.support.v17.leanback.app.PlaybackSupportTestFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml b/v17/leanback/tests/res/layout/rows.xml
similarity index 70%
copy from samples/SupportLeanbackDemos/res/layout/legacy_details.xml
copy to v17/leanback/tests/res/layout/rows.xml
index 4af4e6a..7a7c0b0 100644
--- a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml
+++ b/v17/leanback/tests/res/layout/rows.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
limitations under the License.
-->
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="com.example.android.leanback.DetailsFragment"
- android:id="@+id/details_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main_frame"
android:layout_width="match_parent"
- android:layout_height="match_parent"
-/>
+ android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index 6ffa864..3961f49 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -66,7 +66,7 @@
<public type="attr" name="buttonBarNeutralButtonStyle"/>
<public type="attr" name="buttonBarPositiveButtonStyle"/>
<public type="attr" name="buttonBarStyle"/>
- <public type="attr" name="buttonGravity" />
+ <public type="attr" name="buttonGravity"/>
<public type="attr" name="buttonStyle"/>
<public type="attr" name="buttonStyleSmall"/>
<public type="attr" name="buttonTint"/>
@@ -131,7 +131,7 @@
<public type="attr" name="listPreferredItemPaddingRight"/>
<public type="attr" name="logo"/>
<public type="attr" name="logoDescription"/>
- <public type="attr" name="maxButtonHeight" />
+ <public type="attr" name="maxButtonHeight"/>
<public type="attr" name="measureWithLargestChild"/>
<public type="attr" name="navigationContentDescription"/>
<public type="attr" name="navigationIcon"/>
@@ -166,6 +166,7 @@
<public type="attr" name="spinnerStyle"/>
<public type="attr" name="splitTrack"/>
<public type="attr" name="srcCompat"/>
+ <public type="attr" name="state_above_anchor"/>
<public type="attr" name="submitBackground"/>
<public type="attr" name="subtitle"/>
<public type="attr" name="subtitleTextAppearance"/>
@@ -188,13 +189,15 @@
<public type="attr" name="theme"/>
<public type="attr" name="thickness"/>
<public type="attr" name="thumbTextPadding"/>
+ <public type="attr" name="thumbTint"/>
+ <public type="attr" name="thumbTintMode"/>
<public type="attr" name="tickMark"/>
<public type="attr" name="tickMarkTint"/>
<public type="attr" name="tickMarkTintMode"/>
<public type="attr" name="title"/>
+ <public type="attr" name="titleMargin"/>
<public type="attr" name="titleMarginBottom"/>
<public type="attr" name="titleMarginEnd"/>
- <public type="attr" name="titleMargin"/>
<public type="attr" name="titleMargins"/>
<public type="attr" name="titleMarginStart"/>
<public type="attr" name="titleMarginTop"/>
@@ -204,6 +207,8 @@
<public type="attr" name="toolbarNavigationButtonStyle"/>
<public type="attr" name="toolbarStyle"/>
<public type="attr" name="track"/>
+ <public type="attr" name="trackTint"/>
+ <public type="attr" name="trackTintMode"/>
<public type="attr" name="voiceIcon"/>
<public type="attr" name="windowActionBar"/>
<public type="attr" name="windowActionBarOverlay"/>
diff --git a/v7/appcompat/res-public/values/public_styles.xml b/v7/appcompat/res-public/values/public_styles.xml
index 38efc46..a9956ad 100644
--- a/v7/appcompat/res-public/values/public_styles.xml
+++ b/v7/appcompat/res-public/values/public_styles.xml
@@ -36,6 +36,12 @@
<public type="style" name="TextAppearance.AppCompat.Medium"/>
<public type="style" name="TextAppearance.AppCompat.Medium.Inverse"/>
<public type="style" name="TextAppearance.AppCompat.Menu"/>
+ <public type="style" name="TextAppearance.AppCompat.Notification"/>
+ <public type="style" name="TextAppearance.AppCompat.Notification.Info"/>
+ <public type="style" name="TextAppearance.AppCompat.Notification.Line2"/>
+ <public type="style" name="TextAppearance.AppCompat.Notification.Media"/>
+ <public type="style" name="TextAppearance.AppCompat.Notification.Time"/>
+ <public type="style" name="TextAppearance.AppCompat.Notification.Title"/>
<public type="style" name="TextAppearance.AppCompat.SearchResult.Subtitle"/>
<public type="style" name="TextAppearance.AppCompat.SearchResult.Title"/>
<public type="style" name="TextAppearance.AppCompat.Small"/>
diff --git a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
index db9935d..c271f68 100644
--- a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
@@ -29,11 +29,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <EditText
+ <View
android:id="@+id/editText"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:imeOptions="flagNoExtractUi"/>
+ android:layout_height="20dip"
+ android:focusable="true" />
</android.support.v7.custom.FitWindowsContentLayout>
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
index 3390da2..3fe899d 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -270,10 +270,17 @@
// specified, so it should have its height reduced by the height of the system status
// bar.
+ final int[] contentViewLocationOnScreen = new int[2];
+ mContentView.getLocationOnScreen(contentViewLocationOnScreen);
+ final int statusBarHeight = contentViewLocationOnScreen[1];
// Get the system window top inset that was propagated to the top-level DrawerLayout
// during its layout.
int drawerTopInset = mDrawerLayout.getSystemWindowInsetTop();
- assertTrue("Drawer top inset is positive on L+", drawerTopInset > 0);
+ if (statusBarHeight > 0) {
+ assertEquals("Drawer top inset is positive on L+", statusBarHeight, drawerTopInset);
+ } else {
+ assertEquals("Drawer top inset 0 due to no status bar", 0, drawerTopInset);
+ }
assertEquals("Drawer layout and drawer heights on L+",
drawerLayoutHeight - drawerTopInset, contentHeight);
}
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 6b63089..3e51602 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -4,6 +4,9 @@
dependencies {
compile project(":support-appcompat-v7")
compile project(":support-palette-v7")
+
+ androidTestCompile ("com.android.support.test:runner:${project.rootProject.ext.testRunnerVersion}")
+ testCompile 'junit:junit:4.12'
}
android {
@@ -11,6 +14,8 @@
defaultConfig {
minSdkVersion 9
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
sourceSets {
@@ -23,6 +28,11 @@
'src'
]
main.res.srcDir 'res'
+
+ androidTest.setRoot('tests')
+ androidTest.java.srcDir 'tests/src'
+ androidTest.res.srcDir 'tests/res'
+ androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
}
compileOptions {
diff --git a/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java b/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
index 85071a4..7ade21d 100644
--- a/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
+++ b/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
@@ -121,9 +121,9 @@
Field globalRouterField = routerObj.getClass().getDeclaredField("sStatic");
globalRouterField.setAccessible(true);
Object globalRouterObj = globalRouterField.get(null);
- Method method = globalRouterObj.getClass().getDeclaredMethod("isBluetoothA2dpOn", null);
+ Method method = globalRouterObj.getClass().getDeclaredMethod("isBluetoothA2dpOn");
method.setAccessible(true);
- Object result = method.invoke(globalRouterObj, null);
+ Object result = method.invoke(globalRouterObj);
return (Boolean) result;
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
| NoSuchMethodException | InvocationTargetException e) {
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index ac79f2d..2e5b983 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -378,12 +378,12 @@
}
}
- private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+ static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
public static final RouteComparator sInstance = new RouteComparator();
@Override
public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
- return lhs.getName().compareTo(rhs.getName());
+ return lhs.getName().compareToIgnoreCase(rhs.getName());
}
}
}
diff --git a/v7/mediarouter/tests/AndroidManifest.xml b/v7/mediarouter/tests/AndroidManifest.xml
new file mode 100644
index 0000000..87c4d65
--- /dev/null
+++ b/v7/mediarouter/tests/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.v7.mediarouter.test">
+
+ <uses-sdk
+ android:minSdkVersion="9"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling"/>
+
+ <application android:supportsRtl="true">
+
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.v7.mediarouter.test"/>
+
+</manifest>
diff --git a/v7/mediarouter/tests/NO_DOCS b/v7/mediarouter/tests/NO_DOCS
new file mode 100644
index 0000000..092a39c
--- /dev/null
+++ b/v7/mediarouter/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
new file mode 100644
index 0000000..7a21cdb
--- /dev/null
+++ b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.support.v7.media.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRouteChooserDialogTest {
+ private MediaRouteChooserDialog.RouteComparator mComparator;
+
+ @Before
+ public void setup() {
+ mComparator = new MediaRouteChooserDialog.RouteComparator();
+ }
+
+ @Test
+ public void testRouteComparatorWithSameRouteName() {
+ RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "ROUTE_NAME_1");
+ RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "ROUTE_NAME_1");
+ int result = mComparator.compare(routeInfo1, routeInfo2);
+ assertTrue(result == 0);
+ }
+
+ @Test
+ public void testRouteComparatorWithCommonComparison() {
+ RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "Route ABC");
+ RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "Route XYZ");
+ int result = mComparator.compare(routeInfo1, routeInfo2);
+ assertTrue(result < 0);
+ }
+
+ @Test
+ public void testRouteComparatorWithCaseInsensitiveComparison() {
+ RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "living room abc");
+ RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "LIVING ROOM XYZ");
+ int result1 = mComparator.compare(routeInfo1, routeInfo2);
+ assertTrue(result1 < 0);
+
+ RouteInfo routeInfo3 = TestUtils.createRouteInfo("ROUTE_ID_3", "LIVING ROOM ABC");
+ RouteInfo routeInfo4 = TestUtils.createRouteInfo("ROUTE_ID_4", "living room xyz");
+ int result2 = mComparator.compare(routeInfo3, routeInfo4);
+ assertTrue(result2 < 0);
+ }
+}
diff --git a/v7/mediarouter/tests/src/android/support/v7/media/TestUtils.java b/v7/mediarouter/tests/src/android/support/v7/media/TestUtils.java
new file mode 100644
index 0000000..f92a4d2
--- /dev/null
+++ b/v7/mediarouter/tests/src/android/support/v7/media/TestUtils.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.media;
+
+public class TestUtils {
+ public static MediaRouter.RouteInfo createRouteInfo(String id, String name) {
+ MediaRouteDescriptor descriptor = new MediaRouteDescriptor.Builder(id, name).build();
+ MediaRouter.RouteInfo routeInfo = new MediaRouter.RouteInfo(null, null, null);
+ routeInfo.updateDescriptor(descriptor);
+ return routeInfo;
+ }
+}
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
index 93570de..d092587 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
@@ -84,7 +84,7 @@
void onGenerated(Palette palette);
}
- static final int DEFAULT_RESIZE_BITMAP_AREA = 160 * 160;
+ static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
@@ -909,7 +909,7 @@
if (mResizeArea > 0) {
final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
if (bitmapArea > mResizeArea) {
- scaleRatio = mResizeArea / (double) bitmapArea;
+ scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea);
}
} else if (mResizeMaxDimension > 0) {
final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
diff --git a/v7/recyclerview/src/android/support/v7/widget/GapWorker.java b/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
index 87bc8c4..797ace3 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
@@ -15,6 +15,7 @@
*/
package android.support.v7.widget;
+import android.support.annotation.Nullable;
import android.support.v4.os.TraceCompat;
import android.view.View;
@@ -97,6 +98,7 @@
if (mCount > layout.mPrefetchMaxCountObserved) {
layout.mPrefetchMaxCountObserved = mCount;
+ layout.mPrefetchMaxObservedInInitialPrefetch = nested;
view.mRecycler.updateViewCacheSize();
}
}
@@ -257,7 +259,7 @@
return false;
}
- private RecyclerView.ViewHolder flushWorkWithDeadline(RecyclerView view,
+ private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
if (isPrefetchPositionAttached(view, position)) {
// don't attempt to prefetch attached views
@@ -284,38 +286,45 @@
return holder;
}
+ private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView,
+ long deadlineNs) {
+ if (innerView == null) {
+ return;
+ }
+
+ if (innerView.mDataSetHasChangedAfterLayout
+ && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
+ // RecyclerView has new data, but old attached views. Clear everything, so that
+ // we can prefetch without partially stale data.
+ innerView.removeAndRecycleViews();
+ }
+
+ // do nested prefetch!
+ final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
+ innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
+
+ if (innerPrefetchRegistry.mCount != 0) {
+ try {
+ TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
+ innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
+ for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
+ // Note that we ignore immediate flag for inner items because
+ // we have lower confidence they're needed next frame.
+ final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
+ prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
+ }
+ } finally {
+ TraceCompat.endSection();
+ }
+ }
+ }
+
private void flushTaskWithDeadline(Task task, long deadlineNs) {
long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
- RecyclerView.ViewHolder holder = flushWorkWithDeadline(task.view,
+ RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
task.position, taskDeadlineNs);
if (holder != null && holder.mNestedRecyclerView != null) {
- final RecyclerView innerView = holder.mNestedRecyclerView;
-
- if (innerView.mDataSetHasChangedAfterLayout
- && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
- // RecyclerView has new data, but old attached views. Clear everything, so that
- // we can prefetch without partially stale data.
- innerView.removeAndRecycleViews();
- }
-
- // do nested prefetch!
- final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
- innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
-
- if (innerPrefetchRegistry.mCount != 0) {
- try {
- TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
- innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
- for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
- // Note that we ignore immediate flag for inner items because
- // we have lower confidence they're needed next frame.
- final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
- flushWorkWithDeadline(innerView, innerPosition, deadlineNs);
- }
- } finally {
- TraceCompat.endSection();
- }
- }
+ prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
}
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index 9297522..78a31b4 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -413,14 +413,22 @@
}
/**
- * <p>Returns the amount of extra space that should be laid out by LayoutManager.
- * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of
- * items while smooth scrolling and 0 otherwise. You can override this method to implement your
- * custom layout pre-cache logic.</p>
- * <p>Laying out invisible elements will eventually come with performance cost. On the other
- * hand, in places like smooth scrolling to an unknown location, this extra content helps
- * LayoutManager to calculate a much smoother scrolling; which improves user experience.</p>
- * <p>You can also use this if you are trying to pre-layout your upcoming views.</p>
+ * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p>
+ *
+ * <p>By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page
+ * of items while smooth scrolling and 0 otherwise. You can override this method to implement
+ * your custom layout pre-cache logic.</p>
+ *
+ * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant
+ * performance cost. It's typically only desirable in places like smooth scrolling to an unknown
+ * location, where 1) the extra content helps LinearLayoutManager know in advance when its
+ * target is approaching, so it can decelerate early and smoothly and 2) while motion is
+ * continuous.</p>
+ *
+ * <p>Extending the extra layout space is especially expensive if done while the user may change
+ * scrolling direction. Changing direction will cause the extra layout space to swap to the
+ * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large
+ * enough to handle it.</p>
*
* @return The extra space that should be laid out (in pixels).
*/
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index d192f4d..7027acb 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -74,6 +74,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@@ -188,6 +189,16 @@
*/
private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15;
+ /**
+ * on API 15-, a focused child can still be considered a focused child of RV even after
+ * it's being removed or its focusable flag is set to false. This is because when this focused
+ * child is detached, the reference to this child is not removed in clearFocus. API 16 and above
+ * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus
+ * to request focus on a new child, which will clear the focus on the old (detached) child as a
+ * side-effect.
+ */
+ private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15;
+
static final boolean DISPATCH_TEMP_DETACH = false;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
@@ -3340,9 +3351,28 @@
// only recover focus if RV itself has the focus or the focused view is hidden
if (!isFocused()) {
final View focusedChild = getFocusedChild();
- if (!mChildHelper.isHidden(focusedChild)
- // on API 15, this happens :/.
- && focusedChild.getParent() == this && focusedChild.hasFocus()) {
+ if (IGNORE_DETACHED_FOCUSED_CHILD
+ && (focusedChild.getParent() == null || !focusedChild.hasFocus())) {
+ // Special handling of API 15-. A focused child can be invalid because mFocus is not
+ // cleared when the child is detached (mParent = null),
+ // This happens because clearFocus on API 15- does not invalidate mFocus of its
+ // parent when this child is detached.
+ // For API 16+, this is not an issue because requestFocus takes care of clearing the
+ // prior detached focused child. For API 15- the problem happens in 2 cases because
+ // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called
+ // for the current focused item which calls clearChild or 2. when the prior focused
+ // child is removed, removeDetachedView called in layout step 3 which calls
+ // clearChild. We should ignore this invalid focused child in all our calculations
+ // for the next view to receive focus, and apply the focus recovery logic instead.
+ if (mChildHelper.getChildCount() == 0) {
+ // No children left. Request focus on the RV itself since one of its children
+ // was holding focus previously.
+ requestFocus();
+ return;
+ }
+ } else if (!mChildHelper.isHidden(focusedChild)) {
+ // If the currently focused child is hidden, apply the focus recovery logic.
+ // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/.
return;
}
}
@@ -3585,6 +3615,14 @@
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
+ if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
+ // Initial prefetch has expanded cache, so reset until next prefetch.
+ // This prevents initial prefetches from expanding the cache permanently.
+ mLayout.mPrefetchMaxCountObserved = 0;
+ mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
+ mRecycler.updateViewCacheSize();
+ }
+
mLayout.onLayoutCompleted(mState);
onExitLayoutOrScroll();
resumeRequestLayout(false);
@@ -4992,7 +5030,7 @@
if (oldAverage == 0) {
return newValue;
}
- return (oldAverage / 8 * 7) + (newValue / 8);
+ return (oldAverage / 4 * 3) + (newValue / 4);
}
void factorInCreateTime(int viewType, long createTimeNs) {
@@ -5088,7 +5126,7 @@
*/
static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) {
if (holder.mNestedRecyclerView != null) {
- View item = holder.mNestedRecyclerView;
+ View item = holder.mNestedRecyclerView.get();
while (item != null) {
if (item == holder.itemView) {
return; // match found, don't need to clear
@@ -5469,7 +5507,10 @@
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
- holder.mNestedRecyclerView = findNestedRecyclerView(holder.itemView);
+ RecyclerView innerView = findNestedRecyclerView(holder.itemView);
+ if (innerView != null) {
+ holder.mNestedRecyclerView = new WeakReference<>(innerView);
+ }
}
long end = getNanoTime();
@@ -5704,7 +5745,12 @@
recycled = true;
}
} else {
- holder.mNestedRecyclerView = null;
+ // NOTE: A view can fail to be recycled when it is scrolled off while an animation
+ // runs. In this case, the item is eventually recycled by
+ // ItemAnimatorRestoreListener#onAnimationFinished.
+
+ // TODO: consider cancelling an animation when an item is removed scrollBy,
+ // to return it to the pool faster
if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists");
@@ -6789,10 +6835,19 @@
* Written by {@link GapWorker} when prefetches occur to track largest number of view ever
* requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or
* {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call.
+ *
+ * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)},
+ * will be reset upon layout to prevent initial prefetches (often large, since they're
+ * proportional to expected child count) from expanding cache permanently.
*/
int mPrefetchMaxCountObserved;
/**
+ * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset.
+ */
+ boolean mPrefetchMaxObservedInInitialPrefetch;
+
+ /**
* These measure specs might be the measure specs that were passed into RecyclerView's
* onMeasure method OR fake measure specs created by the RecyclerView.
* For example, when a layout is run, RecyclerView always sets these specs to be
@@ -9860,7 +9915,7 @@
*/
public static abstract class ViewHolder {
public final View itemView;
- RecyclerView mNestedRecyclerView;
+ WeakReference<RecyclerView> mNestedRecyclerView;
int mPosition = NO_POSITION;
int mOldPosition = NO_POSITION;
long mItemId = NO_ID;
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index adbe4dd..19f31f8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -2097,15 +2097,23 @@
if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
mPrefetchDistances = new int[mSpanCount];
}
+
+ int itemPrefetchCount = 0;
for (int i = 0; i < mSpanCount; i++) {
- mPrefetchDistances[i] = mLayoutState.mItemDirection == LAYOUT_START
+ // compute number of pixels past the edge of the viewport that the current span extends
+ int distance = mLayoutState.mItemDirection == LAYOUT_START
? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
: mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
+ if (distance >= 0) {
+ // span extends to the edge, so prefetch next item
+ mPrefetchDistances[itemPrefetchCount] = distance;
+ itemPrefetchCount++;
+ }
}
- Arrays.sort(mPrefetchDistances, 0, mSpanCount);
+ Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
// then assign them in order to the next N views (where N = span count)
- for (int i = 0; i < mSpanCount && mLayoutState.hasMore(state); i++) {
+ for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index deac38c..9fbc967 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -47,6 +47,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -441,21 +442,21 @@
// verify trivial noop case
RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(recyclerView) {};
- holder.mNestedRecyclerView = recyclerView;
+ holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
- assertEquals(recyclerView, holder.mNestedRecyclerView);
+ assertEquals(recyclerView, holder.mNestedRecyclerView.get());
// verify clear case
holder = new RecyclerView.ViewHolder(new View(getContext())) {};
- holder.mNestedRecyclerView = recyclerView;
+ holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
assertNull(holder.mNestedRecyclerView);
// verify more deeply nested case
holder = new RecyclerView.ViewHolder(grandParent) {};
- holder.mNestedRecyclerView = recyclerView;
+ holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
- assertEquals(recyclerView, holder.mNestedRecyclerView);
+ assertEquals(recyclerView, holder.mNestedRecyclerView.get());
}
static class MockLayoutManager extends RecyclerView.LayoutManager {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index f011440..7ab616f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -515,6 +515,50 @@
}
@Test
+ public void prefetchStaggeredPastBoundary() {
+ StaggeredGridLayoutManager sglm =
+ new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+ mRecyclerView.setLayoutManager(sglm);
+ mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new RecyclerView.ViewHolder(new View(getContext())) {};
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ holder.itemView.setMinimumWidth(100);
+ holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
+ }
+
+ @Override
+ public int getItemCount() {
+ return 2;
+ }
+ });
+
+ layout(200, 100);
+ mRecyclerView.scrollBy(0, 50);
+
+ /* Each row is 50 pixels:
+ * ------------- *
+ *___0___|___1___*
+ * 0 | 1 *
+ *_______|___1___*
+ * | 1 *
+ */
+ assertEquals(2, mRecyclerView.getChildCount());
+ assertEquals(0, sglm.getFirstChildPosition());
+ assertEquals(1, sglm.getLastChildPosition());
+
+ // prefetch upward gets nothing
+ CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
+
+ // prefetch downward gets nothing (and doesn't crash...)
+ CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
+ }
+
+ @Test
public void prefetchItemsSkipAnimations() {
LinearLayoutManager llm = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(llm);
@@ -608,7 +652,7 @@
@Override
public boolean matches(Object item) {
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder) item;
- return holder.itemView == holder.mNestedRecyclerView;
+ return holder.itemView == holder.mNestedRecyclerView.get();
}
@Override
@@ -651,7 +695,6 @@
}
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
- private static final int OUTER_ITEM_COUNT = 10;
private boolean mReverseInner;
@@ -672,13 +715,24 @@
}
OuterAdapter(boolean reverseInner) {
+ this(reverseInner, 10);
+ }
+
+ OuterAdapter(boolean reverseInner, int itemCount) {
mReverseInner = reverseInner;
- for (int i = 0; i <= OUTER_ITEM_COUNT; i++) {
+ for (int i = 0; i < itemCount; i++) {
mAdapters.add(new InnerAdapter());
mSavedStates.add(null);
}
}
+ void addItem() {
+ int index = getItemCount();
+ mAdapters.add(new InnerAdapter());
+ mSavedStates.add(null);
+ notifyItemInserted(index);
+ }
+
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mRecyclerView.registerTimePassingMs(5);
@@ -714,7 +768,7 @@
@Override
public int getItemCount() {
- return OUTER_ITEM_COUNT;
+ return mAdapters.size();
}
}
@@ -734,13 +788,14 @@
RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
assertNotNull(holder);
assertNotNull(holder.mNestedRecyclerView);
- CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 0, 1);
+ RecyclerView innerView = holder.mNestedRecyclerView.get();
+ CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
// prefetch 4
- ((LinearLayoutManager) holder.mNestedRecyclerView.getLayoutManager())
+ ((LinearLayoutManager) innerView.getLayoutManager())
.setInitialPrefetchItemCount(4);
mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
- CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 0, 1, 2, 3);
+ CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3);
}
@Test
@@ -755,7 +810,7 @@
RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
// anchor from right side, should see last two positions
- CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 18, 19);
+ CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19);
}
@Test
@@ -805,7 +860,7 @@
assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound);
mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
- RecyclerView innerRecyclerView = holder.mNestedRecyclerView;
+ RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
assertNotNull(innerRecyclerView);
CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1);
@@ -841,7 +896,7 @@
// item 0 is cached
assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0);
- validateRvChildrenValid(holder.mNestedRecyclerView, 2);
+ validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
// try and prefetch it
mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
@@ -849,7 +904,92 @@
// make sure cache's inner items aren't rebound unnecessarily
assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
- validateRvChildrenValid(holder.mNestedRecyclerView, 2);
+ validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
+ }
+
+ @Test
+ public void nestedRemoveAnimatingView() {
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ OuterAdapter outerAdapter = new OuterAdapter(false, 1);
+ mRecyclerView.setAdapter(outerAdapter);
+ mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30));
+
+ layout(200, 200);
+
+ // Insert 3 items - only first one in viewport, so only it animates
+ for (int i = 0; i < 3; i++) {
+ outerAdapter.addItem();
+ }
+ layout(200, 200); // layout again to kick off animation
+
+
+ // item 1 is animating, so scroll it out of viewport
+ mRecyclerView.scrollBy(0, 200);
+
+ // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for...
+ assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
+ assertEquals(1, mRecycler.mCachedViews.size());
+ CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0);
+ assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
+
+ // until animation ends
+ mRecyclerView.getItemAnimator().endAnimations();
+ assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
+ assertEquals(2, mRecycler.mCachedViews.size());
+ CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1);
+ assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
+
+ for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) {
+ assertNotNull(viewHolder.mNestedRecyclerView);
+ }
+ }
+
+ @Test
+ public void nestedExpandCacheCorrectly() {
+ final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE;
+
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ OuterAdapter outerAdapter = new OuterAdapter();
+ mRecyclerView.setAdapter(outerAdapter);
+
+ layout(200, 200);
+
+ mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
+ mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
+
+ // after initial prefetch, view cache max expanded by number of inner items prefetched (2)
+ RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
+ RecyclerView innerView = holder.mNestedRecyclerView.get();
+ assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
+ assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved);
+ assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
+
+ try {
+ // Note: As a hack, we not only must manually dispatch attachToWindow(), but we
+ // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker
+ // is registered to the wrong thread, since @setup is called on a different thread
+ // from @Test. Assert this, so this test can be fixed when setup == test thread.
+ assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size());
+ assertFalse(innerView.isAttachedToWindow());
+ innerView.onAttachedToWindow();
+
+ // bring prefetch view into viewport, at which point it shouldn't have cache expanded...
+ mRecyclerView.scrollBy(0, 100);
+ assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
+ assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved);
+ assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
+
+ // until a valid horizontal prefetch caches an item, and expands view count by one
+ innerView.mPrefetchRegistry.setPrefetchVector(1, 0);
+ innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker
+ assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
+ assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved);
+ assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
+ } finally {
+ if (innerView.isAttachedToWindow()) {
+ innerView.onDetachedFromWindow();
+ }
+ }
}
/**
@@ -949,8 +1089,8 @@
for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) {
// verify that children are attached and valid, since the RVs haven't been rebound
assertNotNull(holder.mNestedRecyclerView);
- assertFalse(holder.mNestedRecyclerView.mDataSetHasChangedAfterLayout);
- validateRvChildrenValid(holder.mNestedRecyclerView, 2);
+ assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout);
+ validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
}
// prefetch the outer item bind, but without enough time to do any inner binds
@@ -963,11 +1103,11 @@
RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
assertNotNull(holder);
assertNotNull(holder.mNestedRecyclerView);
- assertEquals(0, holder.mNestedRecyclerView.mChildHelper.getUnfilteredChildCount());
- assertEquals(0, holder.mNestedRecyclerView.mRecycler.mCachedViews.size());
+ assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount());
+ assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size());
// but if we give it more time to bind items, it'll now acquire its inner items
mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
- CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 0, 1);
+ CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1);
}
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
index 0c324a9..0354d53 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
@@ -421,6 +421,11 @@
*/
private void assertFocusAfterLayout(int focusedChildIndexWhenRecoveryEnabled,
int focusedChildIndexWhenRecoveryDisabled) {
+ if (mDisableAnimation && mDisableRecovery) {
+ // This case is not quite handled properly at the moment. For now, RV may become focused
+ // without re-delivering the focus down to the children. Skip the checks for now.
+ return;
+ }
if (mRecyclerView.getChildCount() == 0) {
assertThat("RV should have focus when it has no children",
mRecyclerView.hasFocus(), is(true));
@@ -428,11 +433,6 @@
mRecyclerView.isFocused(), is(true));
return;
}
- if (mDisableAnimation && mDisableRecovery) {
- // This case is not quite handled properly at the moment. For now, RV may become focused
- // without re-delivering the focus down to the children. Skip the checks for now.
- return;
- }
assertThat("RV should still have focus after layout", mRecyclerView.hasFocus(), is(true));
if ((mDisableRecovery && focusedChildIndexWhenRecoveryDisabled == -1)