Merge "leanback: more test for RTL alignment" into oc-support-26.0-dev
diff --git a/annotations/src/android/support/annotation/MainThread.java b/annotations/src/android/support/annotation/MainThread.java
index 5478751..2f50306 100644
--- a/annotations/src/android/support/annotation/MainThread.java
+++ b/annotations/src/android/support/annotation/MainThread.java
@@ -34,9 +34,17 @@
* @MainThread
* public void deliverResult(D data) { ... }
* </code></pre>
+ *
+ * <p class="note"><b>Note:</b> Ordinarily, an app's main thread is also the UI
+ * thread. However, However, under special circumstances, an app's main thread
+ * might not be its UI thread; for more information, see
+ * <a href="/studio/write/annotations.html#thread-annotations">Thread
+ * annotations</a>.
+ *
+ * @see android.support.annotation.UiThread
*/
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface MainThread {
-}
\ No newline at end of file
+}
diff --git a/annotations/src/android/support/annotation/UiThread.java b/annotations/src/android/support/annotation/UiThread.java
index ef81986..0a9a0c1 100644
--- a/annotations/src/android/support/annotation/UiThread.java
+++ b/annotations/src/android/support/annotation/UiThread.java
@@ -35,9 +35,17 @@
*
* public abstract void setText(@NonNull String text) { ... }
* </code></pre>
+ *
+ * <p class="note"><b>Note:</b> Ordinarily, an app's UI thread is also the main
+ * thread. However, However, under special circumstances, an app's UI thread
+ * might not be its main thread; for more information, see
+ * <a href="/studio/write/annotations.html#thread-annotations">Thread
+ * annotations</a>.
+ *
+ * @see android.support.annotation.MainThread
*/
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface UiThread {
-}
\ No newline at end of file
+}
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index 281d189..aafd5f9 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -12654,7 +12654,8 @@
method public void attachToRecyclerView(android.support.v7.widget.RecyclerView) throws java.lang.IllegalStateException;
method public abstract int[] calculateDistanceToFinalSnap(android.support.v7.widget.RecyclerView.LayoutManager, android.view.View);
method public int[] calculateScrollDistance(int, int);
- method protected android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
+ method protected android.support.v7.widget.RecyclerView.SmoothScroller createScroller(android.support.v7.widget.RecyclerView.LayoutManager);
+ method protected deprecated android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
method public abstract android.view.View findSnapView(android.support.v7.widget.RecyclerView.LayoutManager);
method public abstract int findTargetSnapPosition(android.support.v7.widget.RecyclerView.LayoutManager, int, int);
method public boolean onFling(int, int);
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index 444f433..10da04a 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -604,10 +604,27 @@
@RestrictTo(LIBRARY_GROUP)
protected static class BuilderExtender {
public Notification build(Builder b, NotificationBuilderWithBuilderAccessor builder) {
+ RemoteViews styleContentView = b.mStyle != null
+ ? b.mStyle.makeContentView(builder)
+ : null;
Notification n = builder.build();
- if (b.mContentView != null) {
+ if (styleContentView != null) {
+ n.contentView = styleContentView;
+ } else if (b.mContentView != null) {
n.contentView = b.mContentView;
}
+ if (Build.VERSION.SDK_INT >= 16 && b.mStyle != null) {
+ RemoteViews styleBigContentView = b.mStyle.makeBigContentView(builder);
+ if (styleBigContentView != null) {
+ n.bigContentView = styleBigContentView;
+ }
+ }
+ if (Build.VERSION.SDK_INT >= 21 && b.mStyle != null) {
+ RemoteViews styleHeadsUpContentView = b.mStyle.makeHeadsUpContentView(builder);
+ if (styleHeadsUpContentView != null) {
+ n.headsUpContentView = styleHeadsUpContentView;
+ }
+ }
return n;
}
}
@@ -1913,7 +1930,11 @@
* effect.
*/
public static abstract class Style {
- Builder mBuilder;
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ protected Builder mBuilder;
CharSequence mBigContentTitle;
CharSequence mSummaryText;
boolean mSummaryTextSet = false;
@@ -1947,6 +1968,30 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
+ public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
// TODO: implement for all styles
public void addCompatExtras(Bundle extras) {
}
@@ -2306,6 +2351,26 @@
? makeMessageLine(latestIncomingMessage)
: latestIncomingMessage.getText());
}
+ // Build a fallback BigTextStyle for API 16-23 devices
+ if (Build.VERSION.SDK_INT >= 16) {
+ SpannableStringBuilder completeMessage = new SpannableStringBuilder();
+ boolean showNames = mConversationTitle != null
+ || hasMessagesWithoutSender();
+ for (int i = mMessages.size() - 1; i >= 0; i--) {
+ MessagingStyle.Message message = mMessages.get(i);
+ CharSequence line;
+ line = showNames ? makeMessageLine(message) : message.getText();
+ if (i != mMessages.size() - 1) {
+ completeMessage.insert(0, "\n");
+ }
+ completeMessage.insert(0, line);
+ }
+ NotificationCompatJellybean.addBigTextStyle(builder,
+ null,
+ false,
+ null,
+ completeMessage);
+ }
}
}
@@ -2325,6 +2390,16 @@
return null;
}
+ private boolean hasMessagesWithoutSender() {
+ for (int i = mMessages.size() - 1; i >= 0; i--) {
+ MessagingStyle.Message message = mMessages.get(i);
+ if (message.getSender() == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private CharSequence makeMessageLine(MessagingStyle.Message message) {
BidiFormatter bidi = BidiFormatter.getInstance();
SpannableStringBuilder sb = new SpannableStringBuilder();
diff --git a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
index 47bef3b..cfe13b3 100644
--- a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -241,7 +241,7 @@
}
/**
- * Get a {@link TransportControls} instance for this session.
+ * Gets a {@link TransportControls} instance for this session.
*
* @return A controls instance
*/
@@ -250,7 +250,7 @@
}
/**
- * Send the specified media button event to the session. Only media keys can
+ * Sends the specified media button event to the session. Only media keys can
* be sent by this method, other keys will be ignored.
*
* @param keyEvent The media button event to dispatch.
@@ -264,7 +264,7 @@
}
/**
- * Get the current playback state for this session.
+ * Gets the current playback state for this session.
*
* @return The current PlaybackState or null
*/
@@ -273,7 +273,7 @@
}
/**
- * Get the current metadata for this session.
+ * Gets the current metadata for this session.
*
* @return The current MediaMetadata or null.
*/
@@ -282,7 +282,7 @@
}
/**
- * Get the current play queue for this session if one is set. If you only
+ * Gets the current play queue for this session if one is set. If you only
* care about the current item {@link #getMetadata()} should be used.
*
* @return The current play queue or null.
@@ -292,7 +292,7 @@
}
/**
- * Add a queue item from the given {@code description} at the end of the play queue
+ * Adds a queue item from the given {@code description} at the end of the play queue
* of this session. Not all sessions may support this. To know whether the session supports
* this, get the session's flags with {@link #getFlags()} and check that the flag
* {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
@@ -308,7 +308,7 @@
}
/**
- * Add a queue item from the given {@code description} at the specified position
+ * Adds a queue item from the given {@code description} at the specified position
* in the play queue of this session. Shifts the queue item currently at that position
* (if any) and any subsequent queue items to the right (adds one to their indices).
* Not all sessions may support this. To know whether the session supports this,
@@ -328,7 +328,7 @@
}
/**
- * Remove the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
+ * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
* with the given {@link MediaDescriptionCompat description} in the play queue of the
* associated session. Not all sessions may support this. To know whether the session supports
* this, get the session's flags with {@link #getFlags()} and check that the flag
@@ -345,7 +345,7 @@
}
/**
- * Remove an queue item at the specified position in the play queue
+ * Removes an queue item at the specified position in the play queue
* of this session. Not all sessions may support this. To know whether the session supports
* this, get the session's flags with {@link #getFlags()} and check that the flag
* {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
@@ -368,21 +368,21 @@
}
/**
- * Get the queue title for this session.
+ * Gets the queue title for this session.
*/
public CharSequence getQueueTitle() {
return mImpl.getQueueTitle();
}
/**
- * Get the extras for this session.
+ * Gets the extras for this session.
*/
public Bundle getExtras() {
return mImpl.getExtras();
}
/**
- * Get the rating type supported by the session. One of:
+ * Gets the rating type supported by the session. One of:
* <ul>
* <li>{@link RatingCompat#RATING_NONE}</li>
* <li>{@link RatingCompat#RATING_HEART}</li>
@@ -400,7 +400,7 @@
}
/**
- * Return whether captioning is enabled for this session.
+ * Returns whether captioning is enabled for this session.
*
* @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
*/
@@ -409,7 +409,7 @@
}
/**
- * Get the repeat mode for this session.
+ * Gets the repeat mode for this session.
*
* @return The latest repeat mode set to the session, or
* {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set.
@@ -419,7 +419,7 @@
}
/**
- * Return whether the shuffle mode is enabled for this session.
+ * Returns whether the shuffle mode is enabled for this session.
*
* @return {@code true} if the shuffle mode is enabled, {@code false} if disabled or not set.
* @deprecated Use {@link #getShuffleMode} instead.
@@ -430,7 +430,7 @@
}
/**
- * Get the shuffle mode for this session.
+ * Gets the shuffle mode for this session.
*
* @return The latest shuffle mode set to the session, or
* {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if not set.
@@ -440,7 +440,7 @@
}
/**
- * Get the flags for this session. Flags are defined in
+ * Gets the flags for this session. Flags are defined in
* {@link MediaSessionCompat}.
*
* @return The current set of flags for the session.
@@ -450,7 +450,7 @@
}
/**
- * Get the current playback info for this session.
+ * Gets the current playback info for this session.
*
* @return The current playback info or null.
*/
@@ -459,7 +459,7 @@
}
/**
- * Get an intent for launching UI associated with this session if one
+ * Gets an intent for launching UI associated with this session if one
* exists.
*
* @return A {@link PendingIntent} to launch UI or null.
@@ -469,7 +469,7 @@
}
/**
- * Get the token for the session this controller is connected to.
+ * Gets the token for the session this controller is connected to.
*
* @return The session's token.
*/
@@ -478,7 +478,7 @@
}
/**
- * Set the volume of the output this session is playing on. The command will
+ * Sets the volume of the output this session is playing on. The command will
* be ignored if it does not support
* {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
* {@link AudioManager} may be used to affect the handling.
@@ -493,7 +493,7 @@
}
/**
- * Adjust the volume of the output this session is playing on. The direction
+ * Adjusts the volume of the output this session is playing on. The direction
* must be one of {@link AudioManager#ADJUST_LOWER},
* {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
* The command will be ignored if the session does not support
@@ -538,7 +538,7 @@
}
/**
- * Stop receiving updates on the specified callback. If an update has
+ * Stops receiving updates on the specified callback. If an update has
* already been posted you may still receive it after calling this method.
*
* @param callback The callback to remove
@@ -567,7 +567,7 @@
}
/**
- * Get the session owner's package name.
+ * Gets the session owner's package name.
*
* @return The package name of of the session owner.
*/
@@ -665,7 +665,7 @@
}
/**
- * Override to handle chagnes to the {@link MediaSessionCompat} extras.
+ * Override to handle changes to the {@link MediaSessionCompat} extras.
*
* @param extras The extras that can include other information
* associated with the {@link MediaSessionCompat}.
@@ -728,7 +728,7 @@
}
/**
- * Set the handler to use for pre 21 callbacks.
+ * Sets the handler to use for pre 21 callbacks.
*/
private void setHandler(Handler handler) {
mHandler = new MessageHandler(handler.getLooper());
@@ -1040,7 +1040,7 @@
public abstract void playFromUri(Uri uri, Bundle extras);
/**
- * Play an item with a specific id in the play queue. If you specify an
+ * Plays an item with a specific id in the play queue. If you specify an
* id that is not in the play queue, the behavior is undefined.
*/
public abstract void skipToQueueItem(long id);
@@ -1058,36 +1058,36 @@
public abstract void stop();
/**
- * Move to a new location in the media stream.
+ * Moves to a new location in the media stream.
*
* @param pos Position to move to, in milliseconds.
*/
public abstract void seekTo(long pos);
/**
- * Start fast forwarding. If playback is already fast forwarding this
+ * Starts fast forwarding. If playback is already fast forwarding this
* may increase the rate.
*/
public abstract void fastForward();
/**
- * Skip to the next item.
+ * Skips to the next item.
*/
public abstract void skipToNext();
/**
- * Start rewinding. If playback is already rewinding this may increase
+ * Starts rewinding. If playback is already rewinding this may increase
* the rate.
*/
public abstract void rewind();
/**
- * Skip to the previous item.
+ * Skips to the previous item.
*/
public abstract void skipToPrevious();
/**
- * Rate the current content. This will cause the rating to be set for
+ * Rates the current content. This will cause the rating to be set for
* the current user. The Rating type must match the type returned by
* {@link #getRatingType()}.
*
@@ -1096,14 +1096,14 @@
public abstract void setRating(RatingCompat rating);
/**
- * Enable/disable captioning for this session.
+ * Enables/disables captioning for this session.
*
* @param enabled {@code true} to enable captioning, {@code false} to disable.
*/
public abstract void setCaptioningEnabled(boolean enabled);
/**
- * Set the repeat mode for this session.
+ * Sets the repeat mode for this session.
*
* @param repeatMode The repeat mode. Must be one of the followings:
* {@link PlaybackStateCompat#REPEAT_MODE_NONE},
@@ -1114,7 +1114,7 @@
public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
/**
- * Set the shuffle mode for this session.
+ * Sets the shuffle mode for this session.
*
* @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
* @deprecated Use {@link #setShuffleMode} instead.
@@ -1123,7 +1123,7 @@
public abstract void setShuffleModeEnabled(boolean enabled);
/**
- * Set the shuffle mode for this session.
+ * Sets the shuffle mode for this session.
*
* @param shuffleMode The shuffle mode. Must be one of the followings:
* {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
@@ -1133,7 +1133,7 @@
public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
/**
- * Send a custom action for the {@link MediaSessionCompat} to perform.
+ * Sends a custom action for the {@link MediaSessionCompat} to perform.
*
* @param customAction The action to perform.
* @param args Optional arguments to supply to the
@@ -1143,7 +1143,7 @@
Bundle args);
/**
- * Send the id and args from a custom action for the
+ * Sends the id and args from a custom action for the
* {@link MediaSessionCompat} to perform.
*
* @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
@@ -1190,7 +1190,7 @@
}
/**
- * Get the type of volume handling, either local or remote. One of:
+ * Gets the type of volume handling, either local or remote. One of:
* <ul>
* <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
* <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
@@ -1203,7 +1203,7 @@
}
/**
- * Get the stream this is currently controlling volume on. When the volume
+ * Gets the stream this is currently controlling volume on. When the volume
* type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
* have meaning and should be ignored.
*
@@ -1215,7 +1215,7 @@
}
/**
- * Get the type of volume control that can be used. One of:
+ * Gets the type of volume control that can be used. One of:
* <ul>
* <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
* <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
@@ -1230,7 +1230,7 @@
}
/**
- * Get the maximum volume that may be set for this session.
+ * Gets the maximum volume that may be set for this session.
*
* @return The maximum allowed volume where this session is playing.
*/
@@ -1239,7 +1239,7 @@
}
/**
- * Get the current volume for this session.
+ * Gets the current volume for this session.
*
* @return The current volume where this session is playing.
*/
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 eec69e0..5df4b72 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -115,19 +115,19 @@
public @interface SessionFlags {}
/**
- * Set this flag on the session to indicate that it can handle media button
+ * Sets this flag on the session to indicate that it can handle media button
* events.
*/
public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
/**
- * Set this flag on the session to indicate that it handles transport
+ * Sets this flag on the session to indicate that it handles transport
* control commands through its {@link Callback}.
*/
public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
/**
- * Set this flag on the session to indicate that it handles queue
+ * Sets this flag on the session to indicate that it handles queue
* management commands through its {@link Callback}.
*/
public static final int FLAG_HANDLES_QUEUE_COMMANDS = 1 << 2;
@@ -435,7 +435,7 @@
}
/**
- * Add a callback to receive updates on for the MediaSession. This includes
+ * Adds a callback to receive updates on for the MediaSession. This includes
* media button and volume events. The caller's thread will be used to post
* events.
*
@@ -446,7 +446,7 @@
}
/**
- * Set the callback to receive updates for the MediaSession. This includes
+ * Sets the callback to receive updates for the MediaSession. This includes
* media button and volume events. Set the callback to null to stop
* receiving events.
*
@@ -458,7 +458,7 @@
}
/**
- * Set an intent for launching UI for this Session. This can be used as a
+ * Sets an intent for launching UI for this Session. This can be used as a
* quick link to an ongoing media screen. The intent should be for an
* activity that may be started using
* {@link Activity#startActivity(Intent)}.
@@ -470,7 +470,7 @@
}
/**
- * Set a pending intent for your media button receiver to allow restarting
+ * Sets a pending intent for your media button receiver to allow restarting
* playback after the session has been stopped. If your app is started in
* this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
* the pending intent.
@@ -487,7 +487,7 @@
}
/**
- * Set any flags for the session.
+ * Sets any flags for the session.
*
* @param flags The flags to set for this session.
*/
@@ -496,7 +496,7 @@
}
/**
- * Set the stream this session is playing on. This will affect the system's
+ * Sets the stream this session is playing on. This will affect the system's
* volume handling for this session. If {@link #setPlaybackToRemote} was
* previously called it will stop receiving volume commands and the system
* will begin sending volume changes to the appropriate stream.
@@ -510,7 +510,7 @@
}
/**
- * Configure this session to use remote volume handling. This must be called
+ * Configures this session to use remote volume handling. This must be called
* to receive volume button events, otherwise the system will adjust the
* current stream volume for this session. If {@link #setPlaybackToLocal}
* was previously called that stream will stop receiving volume changes for
@@ -532,7 +532,7 @@
}
/**
- * Set if this session is currently active and ready to receive commands. If
+ * Sets if this session is currently active and ready to receive commands. If
* set to false your session's controller may not be discoverable. You must
* set the session to active before it can start receiving media button
* events or transport commands.
@@ -552,7 +552,7 @@
}
/**
- * Get the current active state of this session.
+ * Gets the current active state of this session.
*
* @return True if the session is active, false otherwise.
*/
@@ -561,7 +561,7 @@
}
/**
- * Send a proprietary event to all MediaControllers listening to this
+ * Sends a proprietary event to all MediaControllers listening to this
* Session. It's up to the Controller/Session owner to determine the meaning
* of any events.
*
@@ -585,7 +585,7 @@
}
/**
- * Retrieve a token object that can be used by apps to create a
+ * Retrieves a token object that can be used by apps to create a
* {@link MediaControllerCompat} for interacting with this session. The
* owner of the session is responsible for deciding how to distribute these
* tokens.
@@ -603,7 +603,7 @@
}
/**
- * Get a controller for this session. This is a convenience method to avoid
+ * Gets a controller for this session. This is a convenience method to avoid
* having to cache your own controller in process.
*
* @return A controller for this session.
@@ -613,7 +613,7 @@
}
/**
- * Update the current playback state.
+ * Updates the current playback state.
*
* @param state The current state of playback
*/
@@ -622,7 +622,7 @@
}
/**
- * Update the current metadata. New metadata can be created using
+ * Updates the current metadata. New metadata can be created using
* {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time
* proportional to the size of the bitmap to replace large bitmaps with a scaled down copy.
*
@@ -634,7 +634,7 @@
}
/**
- * Update the list of items in the play queue. It is an ordered list and
+ * Updates the list of items in the play queue. It is an ordered list and
* should contain the current item, and previous or upcoming items if they
* exist. Specify null if there is no current play queue.
* <p>
@@ -649,7 +649,7 @@
}
/**
- * Set the title of the play queue. The UI should display this title along
+ * Sets the title of the play queue. The UI should display this title along
* with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
* name.
*
@@ -660,7 +660,7 @@
}
/**
- * Set the style of rating used by this session. Apps trying to set the
+ * Sets the style of rating used by this session. Apps trying to set the
* rating should use this style. Must be one of the following:
* <ul>
* <li>{@link RatingCompat#RATING_NONE}</li>
@@ -677,7 +677,7 @@
}
/**
- * Enable/disable captioning for this session.
+ * Enables/disables captioning for this session.
*
* @param enabled {@code true} to enable captioning, {@code false} to disable.
*/
@@ -686,7 +686,7 @@
}
/**
- * Set the repeat mode for this session.
+ * Sets the repeat mode for this session.
* <p>
* Note that if this method is not called before, {@link MediaControllerCompat#getRepeatMode}
* will return {@link PlaybackStateCompat#REPEAT_MODE_NONE}.
@@ -702,7 +702,7 @@
}
/**
- * Set the shuffle mode for this session.
+ * Sets the shuffle mode for this session.
* <p>
* Note that if this method is not called before,
* {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}.
@@ -716,7 +716,7 @@
}
/**
- * Set the shuffle mode for this session.
+ * Sets the shuffle mode for this session.
* <p>
* Note that if this method is not called before, {@link MediaControllerCompat#getShuffleMode}
* will return {@link PlaybackStateCompat#SHUFFLE_MODE_NONE}.
@@ -731,7 +731,7 @@
}
/**
- * Set some extras that can be associated with the
+ * Sets some extras that can be associated with the
* {@link MediaSessionCompat}. No assumptions should be made as to how a
* {@link MediaControllerCompat} will handle these extras. Keys should be
* fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
@@ -1588,7 +1588,7 @@
private Object mItem;
/**
- * Create a new {@link MediaSessionCompat.QueueItem}.
+ * Creates a new {@link MediaSessionCompat.QueueItem}.
*
* @param description The {@link MediaDescriptionCompat} for this item.
* @param id An identifier for this item. It must be unique within the
@@ -1616,14 +1616,14 @@
}
/**
- * Get the description for this item.
+ * Gets the description for this item.
*/
public MediaDescriptionCompat getDescription() {
return mDescription;
}
/**
- * Get the queue id for this item.
+ * Gets the queue id for this item.
*/
public long getQueueId() {
return mId;
@@ -1641,7 +1641,7 @@
}
/**
- * Get the underlying
+ * Gets the underlying
* {@link android.media.session.MediaSession.QueueItem}.
* <p>
* On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index 47c819f..0ebe1ea 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -21,8 +21,7 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
+import android.media.session.MediaSession;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -32,22 +31,11 @@
import android.support.v4.app.BundleCompat;
import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.text.BidiFormatter;
import android.support.v7.appcompat.R;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
import android.widget.RemoteViews;
-import java.util.List;
-
/**
- * An extension of {@link android.support.v4.app.NotificationCompat} which supports
- * {@link android.support.v7.app.NotificationCompat.MediaStyle},
- * {@link android.support.v7.app.NotificationCompat.DecoratedCustomViewStyle},
- * and {@link android.support.v7.app.NotificationCompat.DecoratedMediaCustomViewStyle}.
- * You should start using this variant if you need support any of these styles.
+ * An extension of {@link android.support.v4.app.NotificationCompat} which adds additional styles.
*/
public class NotificationCompat extends android.support.v4.app.NotificationCompat {
@@ -83,283 +71,8 @@
return null;
}
- @RequiresApi(24)
- private static void addStyleToBuilderApi24(NotificationBuilderWithBuilderAccessor builder,
- android.support.v4.app.NotificationCompat.Builder b) {
- if (b.mStyle instanceof DecoratedCustomViewStyle) {
- NotificationCompatImpl24.addDecoratedCustomViewStyle(builder);
- } else if (b.mStyle instanceof DecoratedMediaCustomViewStyle) {
- DecoratedMediaCustomViewStyle mediaStyle = (DecoratedMediaCustomViewStyle) b.mStyle;
- NotificationCompatImpl24.addDecoratedMediaCustomViewStyle(builder,
- mediaStyle.mActionsToShowInCompact,
- mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
- } else if (!(b.mStyle instanceof MessagingStyle)) {
- addStyleGetContentViewLollipop(builder, b);
- }
- }
-
- @RequiresApi(21)
- private static RemoteViews addStyleGetContentViewLollipop(
- NotificationBuilderWithBuilderAccessor builder,
- android.support.v4.app.NotificationCompat.Builder b) {
- if (b.mStyle instanceof MediaStyle) {
- MediaStyle mediaStyle = (MediaStyle) b.mStyle;
- NotificationCompatImpl21.addMediaStyle(builder,
- mediaStyle.mActionsToShowInCompact,
- mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
-
- boolean hasContentView = b.getContentView() != null;
- // If we are on L/M the media notification will only be colored if the expanded version
- // is of media style, so we have to create a custom view for the collapsed version as
- // well in that case.
- boolean isMorL = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
- && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M;
- boolean createCustomContent = hasContentView
- || (isMorL && b.getBigContentView() != null);
- if (b.mStyle instanceof DecoratedMediaCustomViewStyle && createCustomContent) {
- RemoteViews contentViewMedia = NotificationCompatImplBase.overrideContentViewMedia(
- builder, b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo,
- b.mNumber, b.mLargeIcon, b.mSubText, b.mUseChronometer,
- b.getWhenIfShowing(), b.getPriority(), b.mActions,
- mediaStyle.mActionsToShowInCompact, false /* no cancel button on L */,
- null /* cancelButtonIntent */, hasContentView /* isDecoratedCustomView */);
- if (hasContentView) {
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, contentViewMedia,
- b.getContentView());
- }
- setBackgroundColor(b.mContext, contentViewMedia, b.getColor());
- return contentViewMedia;
- }
- return null;
- } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
- return getDecoratedContentView(b);
- }
- return addStyleGetContentViewJellybean(builder, b);
- }
-
- @RequiresApi(16)
- private static RemoteViews addStyleGetContentViewJellybean(
- NotificationBuilderWithBuilderAccessor builder,
- android.support.v4.app.NotificationCompat.Builder b) {
- if (b.mStyle instanceof MessagingStyle) {
- addMessagingFallBackStyle((MessagingStyle) b.mStyle, builder, b);
- }
- return addStyleGetContentViewIcs(builder, b);
- }
-
- private static CharSequence makeMessageLine(android.support.v4.app.NotificationCompat.Builder b,
- MessagingStyle style,
- MessagingStyle.Message m) {
- BidiFormatter bidi = BidiFormatter.getInstance();
- SpannableStringBuilder sb = new SpannableStringBuilder();
- boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
- int color = afterLollipop || Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1
- ? Color.BLACK : Color.WHITE;
- CharSequence replyName = m.getSender();
- if (TextUtils.isEmpty(m.getSender())) {
- replyName = style.getUserDisplayName() == null
- ? "" : style.getUserDisplayName();
- color = afterLollipop && b.getColor() != NotificationCompat.COLOR_DEFAULT
- ? b.getColor()
- : color;
- }
- CharSequence senderText = bidi.unicodeWrap(replyName);
- sb.append(senderText);
- sb.setSpan(makeFontColorSpan(color),
- sb.length() - senderText.length(),
- sb.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
- CharSequence text = m.getText() == null ? "" : m.getText();
- sb.append(" ").append(bidi.unicodeWrap(text));
- return sb;
- }
-
- private static TextAppearanceSpan makeFontColorSpan(int color) {
- return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
- }
-
- @RequiresApi(16)
- private static void addMessagingFallBackStyle(MessagingStyle style,
- NotificationBuilderWithBuilderAccessor builder,
- android.support.v4.app.NotificationCompat.Builder b) {
- SpannableStringBuilder completeMessage = new SpannableStringBuilder();
- List<MessagingStyle.Message> messages = style.getMessages();
- boolean showNames = style.getConversationTitle() != null
- || hasMessagesWithoutSender(style.getMessages());
- for (int i = messages.size() - 1; i >= 0; i--) {
- MessagingStyle.Message m = messages.get(i);
- CharSequence line;
- line = showNames ? makeMessageLine(b, style, m) : m.getText();
- if (i != messages.size() - 1) {
- completeMessage.insert(0, "\n");
- }
- completeMessage.insert(0, line);
- }
- NotificationCompatImplJellybean.addBigTextStyle(builder, completeMessage);
- }
-
- private static boolean hasMessagesWithoutSender(
- List<MessagingStyle.Message> messages) {
- for (int i = messages.size() - 1; i >= 0; i--) {
- MessagingStyle.Message m = messages.get(i);
- if (m.getSender() == null) {
- return true;
- }
- }
- return false;
- }
-
- @RequiresApi(14)
- private static RemoteViews addStyleGetContentViewIcs(
- NotificationBuilderWithBuilderAccessor builder,
- android.support.v4.app.NotificationCompat.Builder b) {
- if (b.mStyle instanceof MediaStyle) {
- MediaStyle mediaStyle = (MediaStyle) b.mStyle;
- boolean isDecorated = b.mStyle instanceof DecoratedMediaCustomViewStyle
- && b.getContentView() != null;
- RemoteViews contentViewMedia = NotificationCompatImplBase.overrideContentViewMedia(
- builder, b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
- b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
- b.getPriority(), b.mActions, mediaStyle.mActionsToShowInCompact,
- mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent, isDecorated);
- if (isDecorated) {
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, contentViewMedia,
- b.getContentView());
- return contentViewMedia;
- }
- } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
- return getDecoratedContentView(b);
- }
- return null;
- }
-
- @RequiresApi(16)
- private static void addBigStyleToBuilderJellybean(Notification n,
- android.support.v4.app.NotificationCompat.Builder b) {
- if (b.mStyle instanceof MediaStyle) {
- MediaStyle mediaStyle = (MediaStyle) b.mStyle;
- RemoteViews innerView = b.getBigContentView() != null
- ? b.getBigContentView()
- : b.getContentView();
- boolean isDecorated = b.mStyle instanceof DecoratedMediaCustomViewStyle
- && innerView != null;
- NotificationCompatImplBase.overrideMediaBigContentView(n, b.mContext,
- b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon,
- b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(), 0,
- b.mActions, mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent,
- isDecorated);
- if (isDecorated) {
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.bigContentView,
- innerView);
- }
- } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
- addDecoratedBigStyleToBuilderJellybean(n, b);
- }
- }
-
- private static RemoteViews getDecoratedContentView(
- android.support.v4.app.NotificationCompat.Builder b) {
- if (b.getContentView() == null) {
- // No special content view
- return null;
- }
- RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
- b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
- b.mNotification.icon, b.mLargeIcon, b.mSubText, b.mUseChronometer,
- b.getWhenIfShowing(), b.getPriority(), b.getColor(),
- R.layout.notification_template_custom_big, false /* fitIn1U */, null /* actions */);
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews,
- b.getContentView());
- return remoteViews;
- }
-
- @RequiresApi(16)
- private static void addDecoratedBigStyleToBuilderJellybean(Notification n,
- android.support.v4.app.NotificationCompat.Builder b) {
- RemoteViews bigContentView = b.getBigContentView();
- RemoteViews innerView = bigContentView != null ? bigContentView : b.getContentView();
- if (innerView == null) {
- // No expandable notification
- return;
- }
- RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
- b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
- n.icon ,b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
- b.getPriority(), b.getColor(), R.layout.notification_template_custom_big,
- false /* fitIn1U */, b.mActions);
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews, innerView);
- n.bigContentView = remoteViews;
- }
-
- @RequiresApi(21)
- private static void addDecoratedHeadsUpToBuilderLollipop(Notification n,
- android.support.v4.app.NotificationCompat.Builder b) {
- RemoteViews headsUp = b.getHeadsUpContentView();
- RemoteViews innerView = headsUp != null ? headsUp : b.getContentView();
- if (headsUp == null) {
- // No expandable notification
- return;
- }
- RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
- b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, n.icon,
- b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(),
- b.getColor(), R.layout.notification_template_custom_big, false /* fitIn1U */,
- b.mActions);
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews, innerView);
- n.headsUpContentView = remoteViews;
- }
-
- @RequiresApi(21)
- private static void addBigStyleToBuilderLollipop(Notification n,
- android.support.v4.app.NotificationCompat.Builder b) {
- RemoteViews innerView = b.getBigContentView() != null
- ? b.getBigContentView()
- : b.getContentView();
- if (b.mStyle instanceof DecoratedMediaCustomViewStyle && innerView != null) {
- NotificationCompatImplBase.overrideMediaBigContentView(n, b.mContext,
- b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon,
- b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(), 0,
- b.mActions, false /* showCancelButton */, null /* cancelButtonIntent */,
- true /* decoratedCustomView */);
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.bigContentView,
- innerView);
- setBackgroundColor(b.mContext, n.bigContentView, b.getColor());
- } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
- addDecoratedBigStyleToBuilderJellybean(n, b);
- }
- }
-
- private static void setBackgroundColor(Context context, RemoteViews views, int color) {
- if (color == COLOR_DEFAULT) {
- color = context.getResources().getColor(
- R.color.notification_material_background_media_default_color);
- }
- views.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", color);
- }
-
- @RequiresApi(21)
- private static void addHeadsUpToBuilderLollipop(Notification n,
- android.support.v4.app.NotificationCompat.Builder b) {
- RemoteViews innerView = b.getHeadsUpContentView() != null
- ? b.getHeadsUpContentView()
- : b.getContentView();
- if (b.mStyle instanceof DecoratedMediaCustomViewStyle && innerView != null) {
- n.headsUpContentView = NotificationCompatImplBase.generateMediaBigView(b.mContext,
- b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
- b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
- b.getPriority(), 0, b.mActions, false /* showCancelButton */,
- null /* cancelButtonIntent */, true /* decoratedCustomView */);
- NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.headsUpContentView,
- innerView);
- setBackgroundColor(b.mContext, n.headsUpContentView, b.getColor());
- } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
- addDecoratedHeadsUpToBuilderLollipop(n, b);
- }
- }
-
/**
- * See {@link android.support.v4.app.NotificationCompat}. In addition to the builder in v4, this
- * builder also supports {@link MediaStyle}.
+ * See {@link android.support.v4.app.NotificationCompat}.
*/
public static class Builder extends android.support.v4.app.NotificationCompat.Builder {
@@ -369,101 +82,6 @@
public Builder(Context context) {
super(context);
}
-
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP)
- @Override
- protected BuilderExtender getExtender() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- return new Api24Extender();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- return new LollipopExtender();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- return new JellybeanExtender();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return new IceCreamSandwichExtender();
- } else {
- return super.getExtender();
- }
- }
- }
-
- @RequiresApi(14)
- private static class IceCreamSandwichExtender extends BuilderExtender {
-
- IceCreamSandwichExtender() {
- }
-
- @Override
- public Notification build(android.support.v4.app.NotificationCompat.Builder b,
- NotificationBuilderWithBuilderAccessor builder) {
- RemoteViews contentView = addStyleGetContentViewIcs(builder, b);
- Notification n = builder.build();
- // The above call might override decorated content views again, let's make sure it
- // sticks.
- if (contentView != null) {
- n.contentView = contentView;
- } else if (b.getContentView() != null) {
- n.contentView = b.getContentView();
- }
- return n;
- }
- }
-
- @RequiresApi(16)
- private static class JellybeanExtender extends BuilderExtender {
-
- JellybeanExtender() {
- }
-
- @Override
- public Notification build(android.support.v4.app.NotificationCompat.Builder b,
- NotificationBuilderWithBuilderAccessor builder) {
- RemoteViews contentView = addStyleGetContentViewJellybean(builder, b);
- Notification n = builder.build();
- // The above call might override decorated content views again, let's make sure it
- // sticks.
- if (contentView != null) {
- n.contentView = contentView;
- }
- addBigStyleToBuilderJellybean(n, b);
- return n;
- }
- }
-
- @RequiresApi(21)
- private static class LollipopExtender extends BuilderExtender {
-
- LollipopExtender() {
- }
-
- @Override
- public Notification build(android.support.v4.app.NotificationCompat.Builder b,
- NotificationBuilderWithBuilderAccessor builder) {
- RemoteViews contentView = addStyleGetContentViewLollipop(builder, b);
- Notification n = builder.build();
- // The above call might override decorated content views again, let's make sure it
- // sticks.
- if (contentView != null) {
- n.contentView = contentView;
- }
- addBigStyleToBuilderLollipop(n, b);
- addHeadsUpToBuilderLollipop(n, b);
- return n;
- }
- }
-
- @RequiresApi(24)
- private static class Api24Extender extends BuilderExtender {
-
- @Override
- public Notification build(android.support.v4.app.NotificationCompat.Builder b,
- NotificationBuilderWithBuilderAccessor builder) {
- addStyleToBuilderApi24(builder, b);
- return builder.build();
- }
}
/**
@@ -564,7 +182,9 @@
* @param show whether to show a cancel button
*/
public MediaStyle setShowCancelButton(boolean show) {
- mShowCancelButton = show;
+ if (Build.VERSION.SDK_INT < 21) {
+ mShowCancelButton = show;
+ }
return this;
}
@@ -578,6 +198,78 @@
mCancelButtonIntent = pendingIntent;
return this;
}
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public void apply(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 21) {
+ builder.getBuilder().setStyle(
+ fillInMediaStyle(new Notification.MediaStyle()));
+ } else if (mShowCancelButton) {
+ builder.getBuilder().setOngoing(true);
+ }
+ }
+
+ @RequiresApi(21)
+ Notification.MediaStyle fillInMediaStyle(Notification.MediaStyle style) {
+ if (mActionsToShowInCompact != null) {
+ style.setShowActionsInCompactView(mActionsToShowInCompact);
+ }
+ if (mToken != null) {
+ style.setMediaSession((MediaSession.Token) mToken.getToken());
+ }
+ return style;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 21) {
+ // No custom content view required
+ return null;
+ }
+ overrideContentView(builder, false);
+ return null;
+ }
+
+ RemoteViews overrideContentView(
+ NotificationBuilderWithBuilderAccessor builder, boolean isDecorated) {
+ return NotificationCompatImplBase.overrideContentViewMedia(
+ builder, mBuilder.mContext, mBuilder.mContentTitle, mBuilder.mContentText,
+ mBuilder.mContentInfo, mBuilder.mNumber, mBuilder.mLargeIcon,
+ mBuilder.mSubText, mBuilder.mUseChronometer, mBuilder.getWhenIfShowing(),
+ mBuilder.getPriority(), mBuilder.mActions, mActionsToShowInCompact,
+ mShowCancelButton, mCancelButtonIntent, isDecorated);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 21) {
+ // No custom content view required
+ return null;
+ }
+ return generateBigContentView(false);
+ }
+
+ RemoteViews generateBigContentView(boolean isDecorated) {
+ return NotificationCompatImplBase.generateMediaBigView(
+ mBuilder.mContext, mBuilder.mContentTitle, mBuilder.mContentText,
+ mBuilder.mContentInfo, mBuilder.mNumber, mBuilder.mLargeIcon,
+ mBuilder.mSubText, mBuilder.mUseChronometer,
+ mBuilder.getWhenIfShowing(), mBuilder.getPriority(), 0,
+ mBuilder.mActions, mShowCancelButton, mCancelButtonIntent,
+ isDecorated);
+ }
}
@@ -613,6 +305,87 @@
public DecoratedCustomViewStyle() {
}
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public void apply(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ builder.getBuilder().setStyle(new Notification.DecoratedCustomViewStyle());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ // No custom content view required
+ return null;
+ }
+ if (mBuilder.getContentView() == null) {
+ // No special content view
+ return null;
+ }
+ return createRemoteViews(mBuilder.getContentView(), false);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ // No custom big content view required
+ return null;
+ }
+ RemoteViews bigContentView = mBuilder.getBigContentView();
+ RemoteViews innerView = bigContentView != null
+ ? bigContentView
+ : mBuilder.getContentView();
+ if (innerView == null) {
+ // No expandable notification
+ return null;
+ }
+ return createRemoteViews(innerView, true);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ // No custom heads up content view required
+ return null;
+ }
+ RemoteViews headsUp = mBuilder.getHeadsUpContentView();
+ RemoteViews innerView = headsUp != null ? headsUp : mBuilder.getContentView();
+ if (headsUp == null) {
+ // No expandable notification
+ return null;
+ }
+ return createRemoteViews(innerView, true);
+ }
+
+ private RemoteViews createRemoteViews(RemoteViews innerView, boolean showActions) {
+ RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
+ mBuilder.mContext, mBuilder.mContentTitle, mBuilder.mContentText,
+ mBuilder.mContentInfo, mBuilder.mNumber, mBuilder.mNotification.icon,
+ mBuilder.mLargeIcon, mBuilder.mSubText, mBuilder.mUseChronometer,
+ mBuilder.getWhenIfShowing(), mBuilder.getPriority(), mBuilder.getColor(),
+ R.layout.notification_template_custom_big,
+ false /* fitIn1U */, showActions ? mBuilder.mActions : null);
+ NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+ remoteViews, innerView);
+ return remoteViews;
+ }
}
/**
@@ -652,5 +425,120 @@
public DecoratedMediaCustomViewStyle() {
}
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public void apply(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ builder.getBuilder().setStyle(
+ fillInMediaStyle(new Notification.DecoratedMediaCustomViewStyle()));
+ } else {
+ super.apply(builder);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ // No custom content view required
+ return null;
+ }
+ boolean hasContentView = mBuilder.getContentView() != null;
+ if (Build.VERSION.SDK_INT >= 21) {
+ // If we are on L/M the media notification will only be colored if the expanded
+ // version is of media style, so we have to create a custom view for the collapsed
+ // version as well in that case.
+ boolean createCustomContent = hasContentView
+ || mBuilder.getBigContentView() != null;
+ if (createCustomContent) {
+ RemoteViews contentView = overrideContentView(builder, hasContentView);
+ if (hasContentView) {
+ NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+ contentView,
+ mBuilder.getContentView());
+ }
+ setBackgroundColor(contentView);
+ return contentView;
+ }
+ } else {
+ RemoteViews contentViewMedia = overrideContentView(builder, hasContentView);
+ if (hasContentView) {
+ NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+ contentViewMedia,
+ mBuilder.getContentView());
+ return contentViewMedia;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ // No custom big content view required
+ return null;
+ }
+ RemoteViews innerView = mBuilder.getBigContentView() != null
+ ? mBuilder.getBigContentView()
+ : mBuilder.getContentView();
+ if (innerView == null) {
+ // No expandable notification
+ return null;
+ }
+ RemoteViews bigContentView = generateBigContentView(true);
+ NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+ bigContentView,
+ innerView);
+ if (Build.VERSION.SDK_INT >= 21) {
+ setBackgroundColor(bigContentView);
+ }
+ return bigContentView;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @Override
+ public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ // No custom heads up content view required
+ return null;
+ }
+ RemoteViews innerView = mBuilder.getHeadsUpContentView() != null
+ ? mBuilder.getHeadsUpContentView()
+ : mBuilder.getContentView();
+ if (innerView == null) {
+ // No expandable notification
+ return null;
+ }
+ RemoteViews headsUpContentView = generateBigContentView(true);
+ NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+ headsUpContentView,
+ innerView);
+ if (Build.VERSION.SDK_INT >= 21) {
+ setBackgroundColor(headsUpContentView);
+ }
+ return headsUpContentView;
+ }
+
+ private void setBackgroundColor(RemoteViews views) {
+ int color = mBuilder.getColor() != COLOR_DEFAULT
+ ? mBuilder.getColor()
+ : mBuilder.mContext.getResources().getColor(
+ R.color.notification_material_background_media_default_color);
+ views.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", color);
+ }
}
}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java
deleted file mode 100644
index 2a4bf7b..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java
+++ /dev/null
@@ -1,38 +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.v7.app;
-
-import android.app.Notification;
-import android.media.session.MediaSession;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(21)
-class NotificationCompatImpl21 {
-
- public static void addMediaStyle(NotificationBuilderWithBuilderAccessor b,
- int[] actionsToShowInCompact,
- Object token) {
- Notification.MediaStyle style = new Notification.MediaStyle(b.getBuilder());
- if (actionsToShowInCompact != null) {
- style.setShowActionsInCompactView(actionsToShowInCompact);
- }
- if (token != null) {
- style.setMediaSession((MediaSession.Token) token);
- }
- }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java
deleted file mode 100644
index cc09bb5..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java
+++ /dev/null
@@ -1,46 +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.v7.app;
-
-import android.app.Notification;
-import android.media.session.MediaSession;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(24)
-class NotificationCompatImpl24 {
-
- public static void addDecoratedCustomViewStyle(NotificationBuilderWithBuilderAccessor b) {
- Notification.Builder builder = b.getBuilder();
- builder.setStyle(new Notification.DecoratedCustomViewStyle());
- }
-
- public static void addDecoratedMediaCustomViewStyle(NotificationBuilderWithBuilderAccessor b,
- int[] actionsToShowInCompact,
- Object token) {
- Notification.Builder builder = b.getBuilder();
- Notification.DecoratedMediaCustomViewStyle style =
- new Notification.DecoratedMediaCustomViewStyle();
- if (actionsToShowInCompact != null) {
- style.setShowActionsInCompactView(actionsToShowInCompact);
- }
- if (token != null) {
- style.setMediaSession((MediaSession.Token) token);
- }
- builder.setStyle(style);
- }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
index c1432c9..5875962 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
@@ -16,7 +16,6 @@
package android.support.v7.app;
-import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
@@ -116,21 +115,6 @@
return view;
}
- @RequiresApi(16)
- public static <T extends NotificationCompatBase.Action> void overrideMediaBigContentView(
- Notification n, Context context, CharSequence contentTitle, CharSequence contentText,
- CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
- boolean useChronometer, long when, int priority, int color, List<T> actions,
- boolean showCancelButton, PendingIntent cancelButtonIntent,
- boolean decoratedCustomView) {
- n.bigContentView = generateMediaBigView(context, contentTitle, contentText, contentInfo,
- number, largeIcon, subText, useChronometer, when, priority, color, actions,
- showCancelButton, cancelButtonIntent, decoratedCustomView);
- if (showCancelButton) {
- n.flags |= Notification.FLAG_ONGOING_EVENT;
- }
- }
-
@RequiresApi(11)
public static <T extends NotificationCompatBase.Action> RemoteViews generateMediaBigView(
Context context, CharSequence contentTitle, CharSequence contentText,
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
deleted file mode 100644
index 2fca0f0..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
+++ /dev/null
@@ -1,31 +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.v7.app;
-
-import android.app.Notification;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(16)
-class NotificationCompatImplJellybean {
-
- public static void addBigTextStyle(NotificationBuilderWithBuilderAccessor b,
- CharSequence bigText) {
- Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(b.getBuilder());
- bigTextStyle.bigText(bigText);
- }
-}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index a72975d..a229846 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -491,6 +491,7 @@
// resolve layout direction
resolveShouldLayoutReverse();
+ final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
@@ -498,6 +499,22 @@
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
+ } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
+ >= mOrientationHelper.getEndAfterPadding()
+ || mOrientationHelper.getDecoratedEnd(focused)
+ <= mOrientationHelper.getStartAfterPadding())) {
+ // This case relates to when the anchor child is the focused view and due to layout
+ // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
+ // up after tapping an EditText which shrinks RV causing the focused view (The tapped
+ // EditText which is the anchor child) to get kicked out of the screen. Will update the
+ // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
+ // the available space in layoutState will be calculated as negative preventing the
+ // focused view from being laid out in fill.
+ // Note that we won't update the anchor position between layout passes (refer to
+ // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
+ // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
+ // child which can change between layout passes).
+ mAnchorInfo.assignFromViewAndKeepVisibleRect(focused);
}
if (DEBUG) {
Log.d(TAG, "Anchor info:" + mAnchorInfo);
diff --git a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
index d63045b..d85a27a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
@@ -19,6 +19,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView.LayoutManager;
+import android.support.v7.widget.RecyclerView.SmoothScroller;
import android.support.v7.widget.RecyclerView.SmoothScroller.ScrollVectorProvider;
import android.util.DisplayMetrics;
import android.view.View;
@@ -159,7 +160,7 @@
return false;
}
- RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
+ SmoothScroller smoothScroller = createScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
@@ -203,9 +204,24 @@
* @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
* {@link RecyclerView}.
*
- * @return a {@link LinearSmoothScroller} which will handle the scrolling.
+ * @return a {@link SmoothScroller} which will handle the scrolling.
*/
@Nullable
+ protected SmoothScroller createScroller(LayoutManager layoutManager) {
+ return createSnapScroller(layoutManager);
+ }
+
+ /**
+ * Creates a scroller to be used in the snapping implementation.
+ *
+ * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+ * {@link RecyclerView}.
+ *
+ * @return a {@link LinearSmoothScroller} which will handle the scrolling.
+ * @deprecated use {@link #createScroller(LayoutManager)} instead.
+ */
+ @Nullable
+ @Deprecated
protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) {
if (!(layoutManager instanceof ScrollVectorProvider)) {
return null;
@@ -281,4 +297,4 @@
*/
public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
int velocityY);
-}
\ No newline at end of file
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index cc6bbe8..13dd1e4 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -325,6 +325,36 @@
}
}
+ class GridEditTextAdapter extends EditTextAdapter {
+
+ Set<Integer> mFullSpanItems = new HashSet<Integer>();
+ int mSpanPerItem = 1;
+
+ GridEditTextAdapter(int count) {
+ this(count, 1);
+ }
+
+ GridEditTextAdapter(int count, int spanPerItem) {
+ super(count);
+ mSpanPerItem = spanPerItem;
+ }
+
+ void setFullSpan(int... items) {
+ for (int i : items) {
+ mFullSpanItems.add(i);
+ }
+ }
+
+ void assignSpanSizeLookup(final GridLayoutManager glm) {
+ glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
+ }
+ });
+ }
+ }
+
class GridTestAdapter extends TestAdapter {
Set<Integer> mFullSpanItems = new HashSet<Integer>();
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 8ca5f4e..90e0536 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -38,10 +38,12 @@
import android.support.v4.view.ViewCompat;
import android.support.v7.recyclerview.test.R;
import android.support.v7.recyclerview.test.SameActivityTestRule;
+import android.text.Editable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -793,6 +795,37 @@
}
}
+ public class EditTextAdapter extends RecyclerView.Adapter<TestViewHolder> {
+
+ final ArrayList<Editable> mEditables;
+ public EditTextAdapter(int count) {
+ mEditables = new ArrayList<>();
+ for (int i = 0; i < count; ++i) {
+ mEditables.add(Editable.Factory.getInstance().newEditable("Sample Text " + i));
+ }
+ }
+
+ @Override
+ public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ final EditText editText = new EditText(parent.getContext());
+ editText.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ final TestViewHolder viewHolder = new TestViewHolder(editText);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder, int position) {
+ ((EditText) holder.itemView).setText(Editable.Factory.getInstance().newEditable(
+ mEditables.get(position)));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mEditables.size();
+ }
+ }
+
public class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
implements AttachDetachCountingAdapter {
@@ -1197,6 +1230,27 @@
}
}
+
+ public static View findFirstFullyVisibleChild(RecyclerView parent) {
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ if (isViewFullyInBound(parent, child)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ public static View findLastFullyVisibleChild(RecyclerView parent) {
+ for (int i = parent.getChildCount() - 1; i >= 0; i--) {
+ View child = parent.getChildAt(i);
+ if (isViewFullyInBound(parent, child)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
/**
* Returns whether a child of RecyclerView is partially in bound. A child is
* partially in-bounds if it's either fully or partially visible on the screen.
@@ -1232,7 +1286,7 @@
* @param child The child view to be checked whether is fully within RV's bounds.
* @return True if the child view is fully visible; false otherwise.
*/
- public boolean isViewFullyInBound(RecyclerView parent, View child) {
+ public static boolean isViewFullyInBound(RecyclerView parent, View child) {
if (child == null) {
return false;
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 5ebebfe..222e79d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -18,6 +18,9 @@
import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@@ -36,9 +39,11 @@
import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.util.TouchUtils;
import android.test.UiThreadTest;
import android.util.SparseIntArray;
import android.util.StateSet;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -168,6 +173,84 @@
}
}
+ @Test
+ public void editTextVisibility() throws Throwable {
+ final int spanCount = 3;
+ final int itemCount = 100;
+
+ RecyclerView recyclerView = new WrappedRecyclerView(getActivity());
+ GridEditTextAdapter editTextAdapter = new GridEditTextAdapter(itemCount) {
+ @Override
+ public TestViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+ // Good to have colors for debugging
+ StateListDrawable stl = new StateListDrawable();
+ stl.addState(new int[]{android.R.attr.state_focused},
+ new ColorDrawable(Color.RED));
+ stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
+ //noinspection deprecation using this for kitkat tests
+ testViewHolder.itemView.setBackgroundDrawable(stl);
+ return testViewHolder;
+ }
+ };
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+ }
+ });
+
+ recyclerView.setLayoutParams(
+ new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+
+ Config config = new Config(spanCount, itemCount);
+ mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
+ config.mReverseLayout);
+ editTextAdapter.assignSpanSizeLookup(mGlm);
+ recyclerView.setAdapter(editTextAdapter);
+ recyclerView.setLayoutManager(mGlm);
+ waitForFirstLayout(recyclerView);
+
+ // First focus on the last fully visible EditText located at span index #1.
+ View toFocus = findLastFullyVisibleChild(mRecyclerView);
+ int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+ focusIndex = (focusIndex / spanCount) * spanCount + 1;
+ toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+ assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+
+ mGlm.expectLayout(1);
+ TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+ mGlm.waitForLayout(5);
+ getInstrumentation().waitForIdleSync();
+ waitForIdleScroll(mRecyclerView);
+ assertThat("Child at position " + focusIndex + " should be focused",
+ toFocus.hasFocus(), is(true));
+ assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(mRecyclerView, toFocus));
+
+ // Close soft input
+ mGlm.expectLayout(1);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+ mGlm.waitForLayout(5);
+ getInstrumentation().waitForIdleSync();
+ waitForIdleScroll(mRecyclerView);
+ assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(mRecyclerView, toFocus));
+
+ // Now focus on the first fully visible EditText located at the last span index.
+ toFocus = findFirstFullyVisibleChild(mRecyclerView);
+ focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+ focusIndex = (focusIndex / spanCount) * spanCount + (spanCount - 1);
+ toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+ mGlm.expectLayout(1);
+ TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+ mGlm.waitForLayout(5);
+ getInstrumentation().waitForIdleSync();
+ waitForIdleScroll(mRecyclerView);
+ assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(mRecyclerView, toFocus));
+ }
@Test
public void topUnfocusableViewsVisibility() throws Throwable {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index a2db640..9d44338 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -18,6 +18,9 @@
import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@@ -35,11 +38,14 @@
import android.support.test.filters.LargeTest;
import android.support.test.filters.SdkSuppress;
import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v7.util.TouchUtils;
import android.util.Log;
import android.util.StateSet;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
import org.junit.Test;
@@ -61,6 +67,89 @@
public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
@Test
+ public void editTextVisibility() throws Throwable {
+
+ // Simulating a scenario where an EditText is tapped (which will receive focus).
+ // The soft keyboard that's opened overlaps the focused EditText which will shrink RV's
+ // padded bounded area. LLM should still lay out the focused EditText so that it becomes
+ // visible above the soft keyboard.
+ // The condition for this test is setting RV's height to a non-exact height, so that measure
+ // is called twice (once with the larger height and another time with smaller height when
+ // the keyboard shows up). To ensure this resizing of RV, SOFT_INPUT_ADJUST_RESIZE is set.
+ final LinearLayout container = new LinearLayout(getActivity());
+ container.setOrientation(LinearLayout.VERTICAL);
+ container.setLayoutParams(
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
+ .LayoutParams.MATCH_PARENT));
+
+ final EditTextAdapter editTextAdapter = new EditTextAdapter(50);
+
+ mRecyclerView = inflateWrappedRV();
+ ViewGroup.LayoutParams lp = mRecyclerView.getLayoutParams();
+ lp.height = WRAP_CONTENT;
+ lp.width = MATCH_PARENT;
+
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setAdapter(editTextAdapter);
+ mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+
+ container.addView(mRecyclerView);
+
+ mLayoutManager.expectLayouts(1);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().getContainer().addView(container);
+ }
+ });
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+ }
+ });
+
+ // First focus on the last fully visible EditText.
+ View toFocus = findLastFullyVisibleChild(mRecyclerView);
+ int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+
+ mLayoutManager.expectLayouts(1);
+ TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+ mLayoutManager.waitForLayout(5);
+ getInstrumentation().waitForIdleSync();
+ waitForIdleScroll(mRecyclerView);
+ assertThat("Child at position " + focusIndex + " should be focused",
+ toFocus.hasFocus(), is(true));
+ // Testing for partial visibility instead of full visibility since TextView calls
+ // requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect
+ // containing the content area. This rect is guaranteed to be fully visible whereas a
+ // portion of TextView could be out of bounds.
+ assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(mRecyclerView, toFocus));
+
+ // Close soft input
+ mLayoutManager.expectLayouts(1);
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+ mLayoutManager.waitForLayout(5);
+ getInstrumentation().waitForIdleSync();
+ waitForIdleScroll(mRecyclerView);
+ assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(mRecyclerView, toFocus));
+
+ // Now focus on the first fully visible EditText.
+ toFocus = findFirstFullyVisibleChild(mRecyclerView);
+ focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+ mLayoutManager.expectLayouts(1);
+ TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
+ mLayoutManager.waitForLayout(5);
+ getInstrumentation().waitForIdleSync();
+ waitForIdleScroll(mRecyclerView);
+ assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
+ isViewPartiallyInBound(mRecyclerView, toFocus));
+ }
+
+ @Test
public void topUnfocusableViewsVisibility() throws Throwable {
// The maximum number of child views that can be visible at any time.
final int visibleChildCount = 5;