am f0fdeabc: Update API file for percent library aspect ratio.
* commit 'f0fdeabc2a533c7771ab4940780879a37dc1fef3':
Update API file for percent library aspect ratio.
diff --git a/customtabs/api/current.txt b/customtabs/api/current.txt
index b66c71e..7343163 100644
--- a/customtabs/api/current.txt
+++ b/customtabs/api/current.txt
@@ -8,6 +8,8 @@
field public static final int NAVIGATION_FAILED = 3; // 0x3
field public static final int NAVIGATION_FINISHED = 2; // 0x2
field public static final int NAVIGATION_STARTED = 1; // 0x1
+ field public static final int TAB_HIDDEN = 6; // 0x6
+ field public static final int TAB_SHOWN = 5; // 0x5
}
public class CustomTabsClient {
@@ -21,9 +23,11 @@
method public void launchUrl(android.app.Activity, android.net.Uri);
field public static final java.lang.String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
field public static final java.lang.String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+ field public static final java.lang.String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
field public static final java.lang.String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
field public static final java.lang.String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
field public static final java.lang.String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+ field public static final java.lang.String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
field public static final java.lang.String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
field public static final java.lang.String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
field public static final java.lang.String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
@@ -41,6 +45,8 @@
ctor public CustomTabsIntent.Builder(android.support.customtabs.CustomTabsSession);
method public android.support.customtabs.CustomTabsIntent.Builder addMenuItem(java.lang.String, android.app.PendingIntent);
method public android.support.customtabs.CustomTabsIntent build();
+ method public android.support.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+ method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent, boolean);
method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent);
method public android.support.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
method public android.support.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, int, int);
diff --git a/customtabs/src/android/support/customtabs/CustomTabsCallback.java b/customtabs/src/android/support/customtabs/CustomTabsCallback.java
index ea5f3e3..d7fdd39 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsCallback.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsCallback.java
@@ -44,6 +44,16 @@
public static final int NAVIGATION_ABORTED = 4;
/**
+ * Sent when the tab becomes visible.
+ */
+ public static final int TAB_SHOWN = 5;
+
+ /**
+ * Sent when the tab becomes hidden.
+ */
+ public static final int TAB_HIDDEN = 6;
+
+ /**
* To be called when a navigation event happens.
*
* @param navigationEvent The code corresponding to the navigation event.
diff --git a/customtabs/src/android/support/customtabs/CustomTabsIntent.java b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
index 2276f94..ab3685f 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsIntent.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
@@ -57,6 +57,12 @@
"android.support.customtabs.extra.TOOLBAR_COLOR";
/**
+ * Boolean extra that enables the url bar to hide as the user scrolls down the page
+ */
+ public static final String EXTRA_ENABLE_URLBAR_HIDING =
+ "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+
+ /**
* Extra bitmap that specifies the icon of the back button on the toolbar. If the client chooses
* not to customize it, a default close button will be used.
*/
@@ -109,6 +115,13 @@
"android.support.customtabs.customaction.PENDING_INTENT";
/**
+ * Extra boolean that specifies whether the custom action button should be tinted. Default is
+ * false and the action button will not be tinted.
+ */
+ public static final String EXTRA_TINT_ACTION_BUTTON =
+ "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+
+ /**
* Use an {@code ArrayList<Bundle>} for specifying menu related params. There should be a
* separate {@link Bundle} for each custom menu item.
*/
@@ -202,6 +215,14 @@
}
/**
+ * Enables the url bar to hide as the user scrolls down on the page.
+ */
+ public Builder enableUrlBarHiding() {
+ mIntent.putExtra(EXTRA_ENABLE_URLBAR_HIDING, true);
+ return this;
+ }
+
+ /**
* Sets the Close button icon for the custom tab.
*
* @param icon The icon {@link Bitmap}
@@ -243,18 +264,29 @@
* @param icon The icon.
* @param description The description for the button. To be used for accessibility.
* @param pendingIntent pending intent delivered when the button is clicked.
+ * @param shouldTint Whether the action button should be tinted.
*/
- public Builder setActionButton(@NonNull Bitmap icon,
- @NonNull String description, @NonNull PendingIntent pendingIntent) {
+ public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
+ @NonNull PendingIntent pendingIntent, boolean shouldTint) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_ICON, icon);
bundle.putString(KEY_DESCRIPTION, description);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
mIntent.putExtra(EXTRA_ACTION_BUTTON_BUNDLE, bundle);
+ mIntent.putExtra(EXTRA_TINT_ACTION_BUTTON, shouldTint);
return this;
}
/**
+ * See {@link CustomTabsIntent.Builder#setActionButton(
+ * Bitmap, String, PendingIntent, boolean)}
+ */
+ public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
+ @NonNull PendingIntent pendingIntent) {
+ return setActionButton(icon, description, pendingIntent, false);
+ }
+
+ /**
* Sets the start animations,
*
* @param context Application context.
diff --git a/customtabs/src/android/support/customtabs/CustomTabsService.java b/customtabs/src/android/support/customtabs/CustomTabsService.java
index 37f1a60..56940e2 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsService.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsService.java
@@ -50,7 +50,7 @@
public static final String KEY_URL =
"android.support.customtabs.otherurls.URL";
- private Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
+ private final Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {
diff --git a/design/api/current.txt b/design/api/current.txt
index de4e09b..30da82a 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -61,6 +61,7 @@
method public int getOverlayTop();
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+ method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
method public void setOverlayTop(int);
}
@@ -84,6 +85,8 @@
method public void setExpandedTitleColor(int);
method public void setExpandedTitleGravity(int);
method public void setExpandedTitleTextAppearance(int);
+ method public void setScrimsShown(boolean);
+ method public void setScrimsShown(boolean, boolean);
method public void setStatusBarScrim(android.graphics.drawable.Drawable);
method public void setStatusBarScrimColor(int);
method public void setStatusBarScrimResource(int);
@@ -178,7 +181,7 @@
field public static final android.os.Parcelable.Creator<android.support.design.widget.CoordinatorLayout.SavedState> CREATOR;
}
- public class FloatingActionButton extends android.widget.ImageView {
+ public class FloatingActionButton extends android.widget.ImageButton {
ctor public FloatingActionButton(android.content.Context);
ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet);
ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet, int);
diff --git a/design/base/android/support/design/widget/StateListAnimator.java b/design/base/android/support/design/widget/StateListAnimator.java
index c937b0b..2de5dba 100644
--- a/design/base/android/support/design/widget/StateListAnimator.java
+++ b/design/base/android/support/design/widget/StateListAnimator.java
@@ -124,8 +124,11 @@
if (mLastMatch != null) {
cancel();
}
+
mLastMatch = match;
- if (match != null) {
+
+ View view = mViewRef.get();
+ if (match != null && view != null && view.getVisibility() == View.VISIBLE ) {
start(match);
}
}
diff --git a/design/base/android/support/design/widget/ValueAnimatorCompat.java b/design/base/android/support/design/widget/ValueAnimatorCompat.java
index 1e33f66..20bebb6 100644
--- a/design/base/android/support/design/widget/ValueAnimatorCompat.java
+++ b/design/base/android/support/design/widget/ValueAnimatorCompat.java
@@ -104,6 +104,7 @@
abstract void cancel();
abstract float getAnimatedFraction();
abstract void end();
+ abstract long getDuration();
}
private final Impl mImpl;
@@ -191,4 +192,8 @@
public void end() {
mImpl.end();
}
+
+ public long getDuration() {
+ return mImpl.getDuration();
+ }
}
diff --git a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
index 6fb3126..fd0b045 100644
--- a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
import android.support.design.R;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.view.View;
@@ -68,7 +69,7 @@
PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) {
// Now we need to tint the original background with the tint, using
// an InsetDrawable if we have a border width
- mShapeDrawable = DrawableCompat.wrap(originalBackground.mutate());
+ mShapeDrawable = DrawableCompat.wrap(mutateDrawable(originalBackground));
DrawableCompat.setTintList(mShapeDrawable, backgroundTint);
if (backgroundTintMode != null) {
DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode);
@@ -110,6 +111,15 @@
updatePadding();
}
+ private static Drawable mutateDrawable(Drawable drawable) {
+ if (Build.VERSION.SDK_INT < 14 && drawable instanceof GradientDrawable) {
+ // GradientDrawable pre-ICS does not copy over it's color when mutated. We just skip
+ // the mutate and hope for the best.
+ return drawable;
+ }
+ return drawable.mutate();
+ }
+
@Override
void setBackgroundTintList(ColorStateList tint) {
DrawableCompat.setTintList(mShapeDrawable, tint);
diff --git a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
index 42cf086..1c708f5 100644
--- a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
@@ -147,6 +147,11 @@
}
}
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
private void update() {
if (mIsRunning) {
// Update the animated fraction
diff --git a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
index 4f9ea39..5ee272b 100644
--- a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
+++ b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
@@ -113,4 +113,9 @@
public void end() {
mValueAnimator.end();
}
+
+ @Override
+ public long getDuration() {
+ return mValueAnimator.getDuration();
+ }
}
diff --git a/design/res/layout/design_layout_tab_icon.xml b/design/res/layout/design_layout_tab_icon.xml
index 6464d1f..5dcfa11 100644
--- a/design/res/layout/design_layout_tab_icon.xml
+++ b/design/res/layout/design_layout_tab_icon.xml
@@ -16,6 +16,6 @@
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"/>
\ No newline at end of file
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:scaleType="centerInside"/>
\ No newline at end of file
diff --git a/design/res/layout/design_layout_tab_text.xml b/design/res/layout/design_layout_tab_text.xml
index a83bb3d..89681c5 100644
--- a/design/res/layout/design_layout_tab_text.xml
+++ b/design/res/layout/design_layout_tab_text.xml
@@ -16,7 +16,7 @@
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
diff --git a/design/res/layout/design_menu_item_action_area.xml b/design/res/layout/design_menu_item_action_area.xml
new file mode 100644
index 0000000..ba8141d
--- /dev/null
+++ b/design/res/layout/design_menu_item_action_area.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
diff --git a/design/res/layout/design_navigation_item.xml b/design/res/layout/design_navigation_item.xml
index 3fcd74a..14c1be3 100644
--- a/design/res/layout/design_navigation_item.xml
+++ b/design/res/layout/design_navigation_item.xml
@@ -19,8 +19,4 @@
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
- android:paddingRight="?attr/listPreferredItemPaddingRight"
- android:drawablePadding="@dimen/design_navigation_icon_padding"
- android:gravity="center_vertical|start"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
+ android:paddingRight="?attr/listPreferredItemPaddingRight"/>
diff --git a/design/res/layout/design_navigation_menu_item.xml b/design/res/layout/design_navigation_menu_item.xml
new file mode 100644
index 0000000..91104bb1
--- /dev/null
+++ b/design/res/layout/design_navigation_menu_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <CheckedTextView
+ android:id="@+id/design_menu_item_text"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:drawablePadding="@dimen/design_navigation_icon_padding"
+ android:gravity="center_vertical|start"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
+
+ <ViewStub
+ android:id="@+id/design_menu_item_action_area_stub"
+ android:inflatedId="@+id/design_menu_item_action_area"
+ android:layout="@layout/design_menu_item_action_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
+
+</merge>
diff --git a/design/res/values-sw600dp/dimens.xml b/design/res/values-sw600dp/dimens.xml
index ebbc20e..8dada20 100644
--- a/design/res/values-sw600dp/dimens.xml
+++ b/design/res/values-sw600dp/dimens.xml
@@ -17,7 +17,7 @@
<resources>
- <dimen name="design_tab_min_width">160dp</dimen>
+ <dimen name="design_tab_scrollable_min_width">160dp</dimen>
<dimen name="design_snackbar_min_width">320dp</dimen>
<dimen name="design_snackbar_max_width">576dp</dimen>
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
index c7f8cef..59d2f1f 100644
--- a/design/res/values/dimens.xml
+++ b/design/res/values/dimens.xml
@@ -31,8 +31,10 @@
<dimen name="design_navigation_padding_top_default">0dp</dimen>
<dimen name="design_navigation_padding_bottom">8dp</dimen>
- <dimen name="design_tab_min_width">72dp</dimen>
+ <dimen name="design_tab_scrollable_min_width">72dp</dimen>
<dimen name="design_tab_max_width">264dp</dimen>
+ <dimen name="design_tab_text_size">14sp</dimen>
+ <dimen name="design_tab_text_size_2line">12sp</dimen>
<dimen name="design_snackbar_min_width">-1px</dimen>
<dimen name="design_snackbar_max_width">-1px</dimen>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index e75d805..fa770aa 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -54,7 +54,7 @@
</style>
<style name="TextAppearance.Design.Tab" parent="TextAppearance.AppCompat.Button">
- <item name="android:textSize">14sp</item>
+ <item name="android:textSize">@dimen/design_tab_text_size</item>
<item name="android:textColor">?android:textColorSecondary</item>
<item name="textAllCaps">true</item>
</style>
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
index 7813163..1d819df 100644
--- a/design/src/android/support/design/internal/NavigationMenuItemView.java
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -27,19 +27,30 @@
import android.support.v4.widget.TextViewCompat;
import android.support.v7.internal.view.menu.MenuItemImpl;
import android.support.v7.internal.view.menu.MenuView;
+import android.support.v7.widget.LinearLayoutCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.widget.TextView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.CheckedTextView;
+import android.widget.FrameLayout;
/**
* @hide
*/
-public class NavigationMenuItemView extends TextView implements MenuView.ItemView {
+public class NavigationMenuItemView extends LinearLayoutCompat implements MenuView.ItemView {
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
- private int mIconSize;
+ private final int mIconSize;
+
+ private final CheckedTextView mTextView;
+
+ private FrameLayout mActionArea;
+
private MenuItemImpl mItemData;
+
private ColorStateList mIconTintList;
public NavigationMenuItemView(Context context) {
@@ -52,8 +63,14 @@
public NavigationMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ setOrientation(HORIZONTAL);
+ LayoutInflater.from(context).inflate(R.layout.design_navigation_menu_item, this, true);
mIconSize = context.getResources().getDimensionPixelSize(
R.dimen.design_navigation_icon_size);
+ mTextView = (CheckedTextView) findViewById(R.id.design_menu_item_text);
+ mTextView.setDuplicateParentStateEnabled(true);
+ // Prevent the action view from stealing the event on the item row.
+ setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
}
@Override
@@ -71,6 +88,18 @@
setEnabled(itemData.isEnabled());
setTitle(itemData.getTitle());
setIcon(itemData.getIcon());
+ setActionView(itemData.getActionView());
+ }
+
+ private void setActionView(View actionView) {
+ if (mActionArea == null) {
+ mActionArea = (FrameLayout) ((ViewStub) findViewById(
+ R.id.design_menu_item_action_area_stub)).inflate();
+ }
+ mActionArea.removeAllViews();
+ if (actionView != null) {
+ mActionArea.addView(actionView);
+ }
}
private StateListDrawable createDefaultBackground() {
@@ -91,7 +120,7 @@
@Override
public void setTitle(CharSequence title) {
- setText(title);
+ mTextView.setText(title);
}
@Override
@@ -102,6 +131,7 @@
@Override
public void setChecked(boolean checked) {
refreshDrawableState();
+ mTextView.setChecked(checked);
}
@Override
@@ -115,7 +145,7 @@
icon.setBounds(0, 0, mIconSize, mIconSize);
DrawableCompat.setTintList(icon, mIconTintList);
}
- TextViewCompat.setCompoundDrawablesRelative(this, icon, null, null, null);
+ TextViewCompat.setCompoundDrawablesRelative(mTextView, icon, null, null, null);
}
@Override
@@ -144,4 +174,13 @@
setIcon(mItemData.getIcon());
}
}
+
+ public void setTextAppearance(Context context, int textAppearance) {
+ mTextView.setTextAppearance(context, textAppearance);
+ }
+
+ public void setTextColor(ColorStateList colors) {
+ mTextView.setTextColor(colors);
+ }
+
}
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index e523c38..e79a421 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -190,10 +190,10 @@
if (positionInAdapter >= 0) {
setUpdateSuspended(true);
MenuItemImpl item = mAdapter.getItem(positionInAdapter).getMenuItem();
- if (item != null && item.isCheckable()) {
+ boolean result = mMenu.performItemAction(item, this, 0);
+ if (item != null && item.isCheckable() && result) {
mAdapter.setCheckedItem(item);
}
- mMenu.performItemAction(item, this, 0);
setUpdateSuspended(false);
updateMenuView(false);
}
@@ -266,6 +266,7 @@
private static final String STATE_CHECKED_ITEM = "android:menu:checked";
+ private static final String STATE_ACTION_VIEWS = "android:menu:action_views";
private static final int VIEW_TYPE_NORMAL = 0;
private static final int VIEW_TYPE_SUBHEADER = 1;
private static final int VIEW_TYPE_SEPARATOR = 2;
@@ -470,6 +471,18 @@
if (mCheckedItem != null) {
state.putInt(STATE_CHECKED_ITEM, mCheckedItem.getItemId());
}
+ // Store the states of the action views.
+ SparseArray<ParcelableSparseArray> actionViewStates = new SparseArray<>();
+ for (NavigationMenuItem navigationMenuItem : mItems) {
+ MenuItemImpl item = navigationMenuItem.getMenuItem();
+ View actionView = item != null ? item.getActionView() : null;
+ if (actionView != null) {
+ ParcelableSparseArray container = new ParcelableSparseArray();
+ actionView.saveHierarchyState(container);
+ actionViewStates.put(item.getItemId(), container);
+ }
+ }
+ state.putSparseParcelableArray(STATE_ACTION_VIEWS, actionViewStates);
return state;
}
@@ -479,7 +492,7 @@
mUpdateSuspended = true;
for (NavigationMenuItem item : mItems) {
MenuItemImpl menuItem = item.getMenuItem();
- if (menuItem != null && menuItem.getItemId() == checkedItem) {
+ if (menuItem != null && menuItem.getItemId() == checkedItem) {
setCheckedItem(menuItem);
break;
}
@@ -487,6 +500,16 @@
mUpdateSuspended = false;
prepareMenuItems();
}
+ // Restore the states of the action views.
+ SparseArray<ParcelableSparseArray> actionViewStates = state
+ .getSparseParcelableArray(STATE_ACTION_VIEWS);
+ for (NavigationMenuItem navigationMenuItem : mItems) {
+ MenuItemImpl item = navigationMenuItem.getMenuItem();
+ View actionView = item != null ? item.getActionView() : null;
+ if (actionView != null) {
+ actionView.restoreHierarchyState(actionViewStates.get(item.getItemId()));
+ }
+ }
}
public void setUpdateSuspended(boolean updateSuspended) {
diff --git a/design/src/android/support/design/internal/ParcelableSparseArray.java b/design/src/android/support/design/internal/ParcelableSparseArray.java
new file mode 100644
index 0000000..74abcc2
--- /dev/null
+++ b/design/src/android/support/design/internal/ParcelableSparseArray.java
@@ -0,0 +1,76 @@
+/*
+ * 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.design.internal;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+/**
+ * @hide
+ */
+public class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
+
+ public ParcelableSparseArray() {
+ super();
+ }
+
+ public ParcelableSparseArray(Parcel source) {
+ super();
+ int size = source.readInt();
+ int[] keys = new int[size];
+ source.readIntArray(keys);
+ Parcelable[] values = source.readParcelableArray(
+ ParcelableSparseArray.class.getClassLoader());
+ for (int i = 0; i < size; ++i) {
+ put(keys[i], values[i]);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ int size = size();
+ int[] keys = new int[size];
+ Parcelable[] values = new Parcelable[size];
+ for (int i = 0; i < size; ++i) {
+ keys[i] = keyAt(i);
+ values[i] = valueAt(i);
+ }
+ parcel.writeInt(size);
+ parcel.writeIntArray(keys);
+ parcel.writeParcelableArray(values, flags);
+ }
+
+ public static final Parcelable.Creator<ParcelableSparseArray> CREATOR
+ = new Creator<ParcelableSparseArray>() {
+ @Override
+ public ParcelableSparseArray createFromParcel(Parcel source) {
+ return new ParcelableSparseArray(source);
+ }
+
+ @Override
+ public ParcelableSparseArray[] newArray(int size) {
+ return new ParcelableSparseArray[size];
+ }
+ };
+
+}
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 53e96ba..262caa0 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -142,6 +142,8 @@
super(context, attrs);
setOrientation(VERTICAL);
+ ThemeUtils.checkAppCompatTheme(context);
+
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
0, R.style.Widget_Design_AppBarLayout);
mTargetElevation = a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0);
@@ -194,13 +196,15 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ invalidateScrollRanges();
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
-
- // Invalidate the scroll ranges
- mTotalScrollRange = INVALID_SCROLL_RANGE;
- mDownPreScrollRange = INVALID_SCROLL_RANGE;
- mDownPreScrollRange = INVALID_SCROLL_RANGE;
+ invalidateScrollRanges();
mHaveChildWithInterpolator = false;
for (int i = 0, z = getChildCount(); i < z; i++) {
@@ -215,6 +219,13 @@
}
}
+ private void invalidateScrollRanges() {
+ // Invalidate the scroll ranges
+ mTotalScrollRange = INVALID_SCROLL_RANGE;
+ mDownPreScrollRange = INVALID_SCROLL_RANGE;
+ mDownScrollRange = INVALID_SCROLL_RANGE;
+ }
+
@Override
public void setOrientation(int orientation) {
if (orientation != VERTICAL) {
@@ -283,7 +294,7 @@
return new LayoutParams(p);
}
- final boolean hasChildWithInterpolator() {
+ private boolean hasChildWithInterpolator() {
return mHaveChildWithInterpolator;
}
@@ -301,9 +312,7 @@
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int childHeight = ViewCompat.isLaidOut(child)
- ? child.getHeight()
- : child.getMeasuredHeight();
+ final int childHeight = child.getMeasuredHeight();
final int flags = lp.mScrollFlags;
if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
@@ -323,25 +332,24 @@
break;
}
}
- final int top = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
- return mTotalScrollRange = (range - top);
+ return mTotalScrollRange = (range - getTopInset());
}
- final boolean hasScrollableChildren() {
+ private boolean hasScrollableChildren() {
return getTotalScrollRange() != 0;
}
/**
* Return the scroll range when scrolling up from a nested pre-scroll.
*/
- final int getUpNestedPreScrollRange() {
+ private int getUpNestedPreScrollRange() {
return getTotalScrollRange();
}
/**
* Return the scroll range when scrolling down from a nested pre-scroll.
*/
- final int getDownNestedPreScrollRange() {
+ private int getDownNestedPreScrollRange() {
if (mDownPreScrollRange != INVALID_SCROLL_RANGE) {
// If we already have a valid value, return it
return mDownPreScrollRange;
@@ -351,9 +359,7 @@
for (int i = getChildCount() - 1; i >= 0; i--) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int childHeight = ViewCompat.isLaidOut(child)
- ? child.getHeight()
- : child.getMeasuredHeight();
+ final int childHeight = child.getMeasuredHeight();
final int flags = lp.mScrollFlags;
if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
@@ -379,7 +385,7 @@
/**
* Return the scroll range when scrolling down from a nested scroll.
*/
- final int getDownNestedScrollRange() {
+ private int getDownNestedScrollRange() {
if (mDownScrollRange != INVALID_SCROLL_RANGE) {
// If we already have a valid value, return it
return mDownScrollRange;
@@ -389,9 +395,7 @@
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- int childHeight = ViewCompat.isLaidOut(child)
- ? child.getHeight()
- : child.getMeasuredHeight();
+ int childHeight = child.getMeasuredHeight();
childHeight += lp.topMargin + lp.bottomMargin;
final int flags = lp.mScrollFlags;
@@ -404,7 +408,7 @@
// For a collapsing exit scroll, we to take the collapsed height into account.
// We also return the range straight away since later views can't scroll
// beneath us
- return range - ViewCompat.getMinimumHeight(child);
+ return mDownScrollRange = (range - ViewCompat.getMinimumHeight(child));
}
} else {
// As soon as a view doesn't have the scroll flag, we end the range calculation.
@@ -454,14 +458,18 @@
return mTargetElevation;
}
- int getPendingAction() {
+ private int getPendingAction() {
return mPendingAction;
}
- void resetPendingAction() {
+ private void resetPendingAction() {
mPendingAction = PENDING_ACTION_NONE;
}
+ private int getTopInset() {
+ return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
+ }
+
private void setWindowInsets(WindowInsetsCompat insets) {
// Invalidate the total scroll range...
mTotalScrollRange = INVALID_SCROLL_RANGE;
@@ -957,8 +965,6 @@
setAppBarTopBottomOffset(parent, abl, 0);
}
}
- // Finally reset the pending state
- abl.resetPendingAction();
} else if (mOffsetToChildIndexOnLayout >= 0) {
View child = abl.getChildAt(mOffsetToChildIndexOnLayout);
int offset = -child.getBottom();
@@ -968,9 +974,12 @@
offset += Math.round(child.getHeight() * mOffsetToChildIndexOnLayoutPerc);
}
setTopAndBottomOffset(offset);
- mOffsetToChildIndexOnLayout = INVALID_POSITION;
}
+ // Finally reset any pending states
+ abl.resetPendingAction();
+ mOffsetToChildIndexOnLayout = INVALID_POSITION;
+
// Make sure we update the elevation
dispatchOffsetUpdates(abl);
@@ -1072,6 +1081,10 @@
}
}
+ if (ViewCompat.getFitsSystemWindows(child)) {
+ childScrollableHeight -= layout.getTopInset();
+ }
+
if (childScrollableHeight > 0) {
final int offsetForView = absOffset - child.getTop();
final int interpolatedDiff = Math.round(childScrollableHeight *
@@ -1199,7 +1212,7 @@
}
@Override
- public boolean onMeasureChild(CoordinatorLayout parent, View child,
+ public boolean onMeasureChild(CoordinatorLayout parent, final View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final int childLpHeight = child.getLayoutParams().height;
@@ -1215,7 +1228,19 @@
}
final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
- if (appBar != null && ViewCompat.isLaidOut(appBar)) {
+ if (appBar != null && appBar.getVisibility() == View.VISIBLE) {
+ if (appBar.getMeasuredHeight() <= 0 && appBar.getMeasuredWidth() <= 0) {
+ // The AppBar hasn't been measured yet, so we'll post a Runnable to delay
+ // our measure until after the ABLs has happened
+ child.post(new Runnable() {
+ @Override
+ public void run() {
+ child.requestLayout();
+ }
+ });
+ return false;
+ }
+
if (ViewCompat.getFitsSystemWindows(appBar)) {
// If the AppBarLayout is fitting system windows then we need to also,
// otherwise we'll get CoL's compatible layout functionality
@@ -1227,6 +1252,7 @@
// If the measure spec doesn't specify a size, use the current height
availableHeight = parent.getHeight();
}
+
final int height = availableHeight - appBar.getMeasuredHeight()
+ appBar.getTotalScrollRange();
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
@@ -1245,32 +1271,64 @@
}
@Override
+ public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
+ // First lay out the child as normal
+ super.onLayoutChild(parent, child, layoutDirection);
+
+ // Now offset us correctly to be in the correct position. This is important for things
+ // like activity transitions which rely on accurate positioning after the first layout.
+ final List<View> dependencies = parent.getDependencies(child);
+ for (int i = 0, z = dependencies.size(); i < z; i++) {
+ if (updateOffset(parent, child, dependencies.get(i))) {
+ // If we updated the offset, break out of the loop now
+ break;
+ }
+ }
+ return true;
+ }
+
+ @Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
+ updateOffset(parent, child, dependency);
+ return false;
+ }
+
+ private boolean updateOffset(CoordinatorLayout parent, View child, View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child so that it is below the app-bar (with any overlap)
-
- final int appBarOffset = ((Behavior) behavior)
- .getTopBottomOffsetForScrollingSibling();
- final int expandedMax = dependency.getHeight() - mOverlayTop;
- final int collapsedMin = parent.getHeight() - child.getHeight();
-
- if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
- // If we have an overlap top, and the dependency is an AppBarLayout, we control
- // the offset ourselves based on the appbar's scroll progress. This is so that
- // the scroll happens sequentially rather than linearly
- final int scrollRange = ((AppBarLayout) dependency).getTotalScrollRange();
- setTopAndBottomOffset(AnimationUtils.lerp(expandedMax, collapsedMin,
- Math.abs(appBarOffset) / (float) scrollRange));
- } else {
- setTopAndBottomOffset(dependency.getHeight() - mOverlayTop + appBarOffset);
- }
+ final int offset = ((Behavior) behavior).getTopBottomOffsetForScrollingSibling();
+ setTopAndBottomOffset(dependency.getHeight() + offset
+ - getOverlapForOffset(dependency, offset));
+ return true;
}
return false;
}
+ private int getOverlapForOffset(final View dependency, final int offset) {
+ if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
+ final AppBarLayout abl = (AppBarLayout) dependency;
+ final int totalScrollRange = abl.getTotalScrollRange();
+ final int preScrollDown = abl.getDownNestedPreScrollRange();
+
+ if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) {
+ // If we're in a pre-scroll down. Don't use the offset at all.
+ return 0;
+ } else {
+ final int availScrollRange = totalScrollRange - preScrollDown;
+ if (availScrollRange == 0) {
+ // Else we'll use a interpolated ratio of the overlap, depending on offset
+ final float percScrolled = offset / (float) availScrollRange;
+ return MathUtils.constrain(
+ Math.round((1f + percScrolled) * mOverlayTop), 0, mOverlayTop);
+ }
+ }
+ }
+ return mOverlayTop;
+ }
+
/**
* Set the distance that this view should overlap any {@link AppBarLayout}.
*
diff --git a/design/src/android/support/design/widget/CollapsingTextHelper.java b/design/src/android/support/design/widget/CollapsingTextHelper.java
index 082c316..9dc60d4 100644
--- a/design/src/android/support/design/widget/CollapsingTextHelper.java
+++ b/design/src/android/support/design/widget/CollapsingTextHelper.java
@@ -93,6 +93,12 @@
private Interpolator mPositionInterpolator;
private Interpolator mTextSizeInterpolator;
+ private float mCollapsedShadowRadius, mCollapsedShadowDx, mCollapsedShadowDy;
+ private int mCollapsedShadowColor;
+
+ private float mExpandedShadowRadius, mExpandedShadowDx, mExpandedShadowDy;
+ private int mExpandedShadowColor;
+
public CollapsingTextHelper(View view) {
mView = view;
@@ -195,6 +201,10 @@
mCollapsedTextSize = a.getDimensionPixelSize(
R.styleable.TextAppearance_android_textSize, (int) mCollapsedTextSize);
}
+ mCollapsedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
+ mCollapsedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
+ mCollapsedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
+ mCollapsedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
a.recycle();
recalculate();
@@ -210,6 +220,10 @@
mExpandedTextSize = a.getDimensionPixelSize(
R.styleable.TextAppearance_android_textSize, (int) mExpandedTextSize);
}
+ mExpandedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
+ mExpandedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
+ mExpandedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
+ mExpandedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
a.recycle();
recalculate();
@@ -258,8 +272,10 @@
}
private void calculateCurrentOffsets() {
- final float fraction = mExpandedFraction;
+ calculateOffsets(mExpandedFraction);
+ }
+ private void calculateOffsets(final float fraction) {
interpolateBounds(fraction);
mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction,
mPositionInterpolator);
@@ -277,6 +293,12 @@
mTextPaint.setColor(mCollapsedTextColor);
}
+ mTextPaint.setShadowLayer(
+ lerp(mExpandedShadowRadius, mCollapsedShadowRadius, fraction, null),
+ lerp(mExpandedShadowDx, mCollapsedShadowDx, fraction, null),
+ lerp(mExpandedShadowDy, mCollapsedShadowDy, fraction, null),
+ blendColors(mExpandedShadowColor, mCollapsedShadowColor, fraction));
+
ViewCompat.postInvalidateOnAnimation(mView);
}
@@ -476,8 +498,7 @@
return;
}
- mTextPaint.setTextSize(mExpandedTextSize);
- mTextPaint.setColor(mExpandedTextColor);
+ calculateOffsets(0f);
mTextureAscent = mTextPaint.ascent();
mTextureDescent = mTextPaint.descent();
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 230a1d0..3f5382e 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -126,6 +126,8 @@
public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ ThemeUtils.checkAppCompatTheme(context);
+
mCollapsingTextHelper = new CollapsingTextHelper(this);
mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
@@ -360,6 +362,22 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ // Update the collapsed bounds by getting it's transformed bounds. This needs to be done
+ // before the children are offset below
+ if (mCollapsingTitleEnabled && mDummyView != null) {
+ ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
+ mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom - mTmpRect.height(),
+ mTmpRect.right, bottom);
+ // Update the expanded bounds
+ mCollapsingTextHelper.setExpandedBounds(
+ mExpandedMarginLeft,
+ mTmpRect.bottom + mExpandedMarginTop,
+ right - left - mExpandedMarginRight,
+ bottom - top - mExpandedMarginBottom);
+
+ mCollapsingTextHelper.recalculate();
+ }
+
// Update our child view offset helpers
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
@@ -376,21 +394,6 @@
getViewOffsetHelper(child).onViewLayout();
}
- // Update the collapsed bounds by getting it's transformed bounds
- if (mCollapsingTitleEnabled && mDummyView != null) {
- ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
- mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom - mTmpRect.height(),
- mTmpRect.right, bottom);
- // Update the expanded bounds
- mCollapsingTextHelper.setExpandedBounds(
- mExpandedMarginLeft,
- mTmpRect.bottom + mExpandedMarginTop,
- right - left - mExpandedMarginRight,
- bottom - top - mExpandedMarginBottom);
-
- mCollapsingTextHelper.recalculate();
- }
-
// Finally, set our minimum height to enable proper AppBarLayout collapsing
if (mToolbar != null) {
if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) {
@@ -462,25 +465,38 @@
return mCollapsingTitleEnabled;
}
- private void showScrim() {
- if (!mScrimsAreShown) {
- if (ViewCompat.isLaidOut(this) && !isInEditMode()) {
- animateScrim(255);
- } else {
- setScrimAlpha(255);
- }
- mScrimsAreShown = true;
- }
+ /**
+ * Set whether the content scrim and/or status bar scrim should be shown or not. Any change
+ * in the vertical scroll may overwrite this value. Any visibility change will be animated if
+ * this view has already been laid out.
+ *
+ * @param shown whether the scrims should be shown
+ *
+ * @see #getStatusBarScrim()
+ * @see #getContentScrim()
+ */
+ public void setScrimsShown(boolean shown) {
+ setScrimsShown(shown, ViewCompat.isLaidOut(this) && !isInEditMode());
}
- private void hideScrim() {
- if (mScrimsAreShown) {
- if (ViewCompat.isLaidOut(this) && !isInEditMode()) {
- animateScrim(0);
+ /**
+ * Set whether the content scrim and/or status bar scrim should be shown or not. Any change
+ * in the vertical scroll may overwrite this value.
+ *
+ * @param shown whether the scrims should be shown
+ * @param animate whether to animate the visibility change
+ *
+ * @see #getStatusBarScrim()
+ * @see #getContentScrim()
+ */
+ public void setScrimsShown(boolean shown, boolean animate) {
+ if (mScrimsAreShown != shown) {
+ if (animate) {
+ animateScrim(shown ? 0xFF : 0x0);
} else {
- setScrimAlpha(0);
+ setScrimAlpha(shown ? 0xFF : 0x0);
}
- mScrimsAreShown = false;
+ mScrimsAreShown = shown;
}
}
@@ -529,11 +545,14 @@
if (mContentScrim != null) {
mContentScrim.setCallback(null);
}
-
- mContentScrim = drawable;
- drawable.setBounds(0, 0, getWidth(), getHeight());
- drawable.setCallback(this);
- drawable.mutate().setAlpha(mScrimAlpha);
+ if (drawable != null) {
+ mContentScrim = drawable.mutate();
+ drawable.setBounds(0, 0, getWidth(), getHeight());
+ drawable.setCallback(this);
+ drawable.setAlpha(mScrimAlpha);
+ } else {
+ mContentScrim = null;
+ }
ViewCompat.postInvalidateOnAnimation(this);
}
}
@@ -878,11 +897,7 @@
// Show or hide the scrims if needed
if (mContentScrim != null || mStatusBarScrim != null) {
- if (getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop) {
- showScrim();
- } else {
- hideScrim();
- }
+ setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop);
}
if (mStatusBarScrim != null && insetTop > 0) {
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 656ce566..617b307 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -168,6 +168,8 @@
public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ ThemeUtils.checkAppCompatTheme(context);
+
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
defStyleAttr, R.style.Widget_Design_CoordinatorLayout);
final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
@@ -441,7 +443,7 @@
if (mBehaviorTouchView == null) {
handled |= super.onTouchEvent(ev);
} else if (cancelSuper) {
- if (cancelEvent != null) {
+ if (cancelEvent == null) {
final long now = SystemClock.uptimeMillis();
cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
@@ -2246,10 +2248,6 @@
/**
* Get the id of this view's anchor.
*
- * <p>The view with this id must be a descendant of the CoordinatorLayout containing
- * the child view this LayoutParams belongs to. It may not be the child view with
- * this LayoutParams or a descendant of it.</p>
- *
* @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor
*/
public int getAnchorId() {
@@ -2257,7 +2255,7 @@
}
/**
- * Get the id of this view's anchor.
+ * Set the id of this view's anchor.
*
* <p>The view with this id must be a descendant of the CoordinatorLayout containing
* the child view this LayoutParams belongs to. It may not be the child view with
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 9a7b727..fda99852 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -31,6 +31,7 @@
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.ImageButton;
import android.widget.ImageView;
import java.util.List;
@@ -53,7 +54,7 @@
* @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
*/
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
-public class FloatingActionButton extends ImageView {
+public class FloatingActionButton extends ImageButton {
// These values must match those in the attrs declaration
private static final int SIZE_MINI = 1;
@@ -82,6 +83,8 @@
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ ThemeUtils.checkAppCompatTheme(context);
+
mShadowPadding = new Rect();
TypedArray a = context.obtainStyledAttributes(attrs,
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index 4e325fa..45fd088 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -95,6 +95,8 @@
public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ ThemeUtils.checkAppCompatTheme(context);
+
// Create the menu
mMenu = new NavigationMenu(context);
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index b04c706..0fccafe 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -179,6 +179,8 @@
mParent = parent;
mContext = parent.getContext();
+ ThemeUtils.checkAppCompatTheme(mContext);
+
LayoutInflater inflater = LayoutInflater.from(mContext);
mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false);
}
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 7eee77b..5f38d11 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -19,6 +19,7 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -38,8 +39,10 @@
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.internal.widget.TintManager;
+import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -59,10 +62,6 @@
import java.util.ArrayList;
import java.util.Iterator;
-import static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
-import static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
-import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
-
/**
* TabLayout provides a horizontal layout to display tabs.
*
@@ -95,6 +94,9 @@
*/
public class TabLayout extends HorizontalScrollView {
+ private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps
+ private static final int DEFAULT_GAP_TEXT_ICON = 8; // dps
+ private static final int INVALID_WIDTH = -1;
private static final int DEFAULT_HEIGHT = 48; // dps
private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps
private static final int FIXED_WRAP_GUTTER_MIN = 16; //dps
@@ -193,12 +195,15 @@
private int mTabTextAppearance;
private ColorStateList mTabTextColors;
+ private float mTabTextSize;
+ private float mTabTextMultiLineSize;
private final int mTabBackgroundResId;
- private final int mTabMinWidth;
private int mTabMaxWidth = Integer.MAX_VALUE;
+ private final int mRequestedTabMinWidth;
private final int mRequestedTabMaxWidth;
+ private final int mScrollableTabMinWidth;
private int mContentInsetStart;
@@ -222,10 +227,10 @@
public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ ThemeUtils.checkAppCompatTheme(context);
+
// Disable the Scroll Bar
setHorizontalScrollBarEnabled(false);
- // Set us to fill the View port
- setFillViewport(true);
// Add the TabStrip
mTabStrip = new SlidingTabStrip(context);
@@ -238,9 +243,6 @@
a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
- mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
- R.style.TextAppearance_Design_Tab);
-
mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
.getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
@@ -252,8 +254,18 @@
mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
mTabPaddingBottom);
- // Text colors come from the text appearance first
- mTabTextColors = loadTextColorFromTextAppearance(mTabTextAppearance);
+ mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
+ R.style.TextAppearance_Design_Tab);
+
+ // Text colors/sizes come from the text appearance first
+ final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
+ R.styleable.TextAppearance);
+ try {
+ mTabTextSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0);
+ mTabTextColors = ta.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ } finally {
+ ta.recycle();
+ }
if (a.hasValue(R.styleable.TabLayout_tabTextColor)) {
// If we have an explicit text color set, use it instead
@@ -268,14 +280,21 @@
mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected);
}
- mTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, 0);
- mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, 0);
+ mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth,
+ INVALID_WIDTH);
+ mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth,
+ INVALID_WIDTH);
mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
a.recycle();
+ // TODO add attr for these
+ final Resources res = getResources();
+ mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
+ mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
+
// Now apply the tab mode and gravity
applyModeAndGravity();
}
@@ -634,6 +653,7 @@
private TabView createTabView(Tab tab) {
final TabView tabView = new TabView(getContext(), tab);
tabView.setFocusable(true);
+ tabView.setMinimumWidth(getTabMinWidth());
if (mTabClickListener == null) {
mTabClickListener = new View.OnClickListener() {
@@ -706,7 +726,7 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If we have a MeasureSpec which allows us to decide our height, try and use the default
// height
- final int idealHeight = dpToPx(DEFAULT_HEIGHT) + getPaddingTop() + getPaddingBottom();
+ final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom();
switch (MeasureSpec.getMode(heightMeasureSpec)) {
case MeasureSpec.AT_MOST:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
@@ -718,39 +738,45 @@
break;
}
+ final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
+ if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
+ // If we don't have an unspecified width spec, use the given size to calculate
+ // the max tab width
+ mTabMaxWidth = mRequestedTabMaxWidth > 0
+ ? mRequestedTabMaxWidth
+ : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN);
+ }
+
// Now super measure itself using the (possibly) modified height spec
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mMode == MODE_FIXED && getChildCount() == 1) {
+ if (getChildCount() == 1) {
// If we're in fixed mode then we need to make the tab strip is the same width as us
// so we don't scroll
final View child = getChildAt(0);
- final int width = getMeasuredWidth();
+ boolean remeasure = false;
- if (child.getMeasuredWidth() > width) {
- // If the child is wider than us, re-measure it with a widthSpec set to exact our
- // measure width
+ switch (mMode) {
+ case MODE_SCROLLABLE:
+ // We only need to resize the child if it's smaller than us. This is similar
+ // to fillViewport
+ remeasure = child.getMeasuredWidth() < getMeasuredWidth();
+ break;
+ case MODE_FIXED:
+ // Resize the child so that it doesn't scroll
+ remeasure = child.getMeasuredWidth() != getMeasuredWidth();
+ break;
+ }
+
+ if (remeasure) {
+ // Re-measure the child with a widthSpec set to be exactly our measure width
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
+ getPaddingBottom(), child.getLayoutParams().height);
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ getMeasuredWidth(), MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
-
- // Now update the tab max width. We do it here as the default tab min width is
- // layout width - 56dp
- int maxTabWidth = mRequestedTabMaxWidth;
- final int defaultTabMaxWidth = getMeasuredWidth() - dpToPx(TAB_MIN_WIDTH_MARGIN);
- if (maxTabWidth == 0 || maxTabWidth > defaultTabMaxWidth) {
- // If the request tab max width is 0, or larger than our default, use the default
- maxTabWidth = defaultTabMaxWidth;
- }
-
- if (mTabMaxWidth != maxTabWidth) {
- // If the tab max width has changed, re-measure
- mTabMaxWidth = maxTabWidth;
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
}
private void removeTabViewAt(int position) {
@@ -819,7 +845,9 @@
}
} else {
final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
- setSelectedTabView(newPosition);
+ if (newPosition != Tab.INVALID_POSITION) {
+ setSelectedTabView(newPosition);
+ }
if (updateIndicator) {
if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
&& newPosition != Tab.INVALID_POSITION) {
@@ -873,14 +901,17 @@
break;
}
- updateTabViewsLayoutParams();
+ updateTabViews(true);
}
- private void updateTabViewsLayoutParams() {
+ private void updateTabViews(final boolean requestLayout) {
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
View child = mTabStrip.getChildAt(i);
+ child.setMinimumWidth(getTabMinWidth());
updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
- child.requestLayout();
+ if (requestLayout) {
+ child.requestLayout();
+ }
}
}
@@ -1138,6 +1169,8 @@
private TextView mCustomTextView;
private ImageView mCustomIconView;
+ private int mDefaultMaxLines = 2;
+
public TabView(Context context, Tab tab) {
super(context);
mTab = tab;
@@ -1147,6 +1180,7 @@
ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
mTabPaddingEnd, mTabPaddingBottom);
setGravity(Gravity.CENTER);
+ setOrientation(VERTICAL);
update();
}
@@ -1184,16 +1218,58 @@
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int measuredWidth = getMeasuredWidth();
- if (measuredWidth < mTabMinWidth || measuredWidth > mTabMaxWidth) {
- // Re-measure if we are outside our min or max width
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(
- MathUtils.constrain(measuredWidth, mTabMinWidth, mTabMaxWidth),
- MeasureSpec.EXACTLY);
+ final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int maxWidth = getTabMaxWidth();
+ if (maxWidth > 0 && (specWidth == 0 || specWidth > maxWidth)) {
+ // If we have a max width and a given spec size which is either unspecified or
+ // larger than the spec size, use a AT_MOST spec
+ super.onMeasure(MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST),
+ heightMeasureSpec);
+ } else {
+ // Else, use the original specs
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+
+ // We need to switch the text size based on whether the text is spanning 2 lines or not
+ if (mTextView != null) {
+ final Resources res = getResources();
+ float textSize = mTabTextSize;
+ int maxLines = mDefaultMaxLines;
+
+ if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
+ // If the icon view is being displayed, we limit the text to 1 line
+ maxLines = 1;
+ } else if (mTextView != null && mTextView.getLineCount() > 1) {
+ // Otherwise when we have text which wraps we reduce the text size
+ textSize = mTabTextMultiLineSize;
+ }
+
+ final float curTextSize = mTextView.getTextSize();
+ final int curLineCount = mTextView.getLineCount();
+
+ if (textSize != curTextSize || maxLines != mTextView.getMaxLines()) {
+ // We've got a new text size and/or max lines...
+ boolean updateTextView = true;
+
+ if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) {
+ // If we're in fixed mode, going up in text size and currently have 1 line
+ // then it's very easy to get into an infinite recursion.
+ // To combat that we check to see if the change in text size
+ // will cause a line count change. If so, abort the size change.
+ final Layout layout = mTextView.getLayout();
+ if (layout == null
+ || approximateLineWidth(layout, 0, textSize) > layout.getWidth()) {
+ updateTextView = false;
+ }
+ }
+
+ if (updateTextView) {
+ mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ mTextView.setMaxLines(maxLines);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
}
final void update() {
@@ -1217,6 +1293,9 @@
}
mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
+ if (mCustomTextView != null) {
+ mDefaultMaxLines = mCustomTextView.getMaxLines();
+ }
mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
} else {
// We do not have a custom view. Remove one if it already exists
@@ -1241,6 +1320,7 @@
.inflate(R.layout.design_layout_tab_text, this, false);
addView(textView);
mTextView = textView;
+ mDefaultMaxLines = mTextView.getMaxLines();
}
mTextView.setTextAppearance(getContext(), mTabTextAppearance);
if (mTabTextColors != null) {
@@ -1284,6 +1364,19 @@
}
}
+ if (iconView != null) {
+ MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams());
+ int bottomMargin = 0;
+ if (hasText && iconView.getVisibility() == VISIBLE) {
+ // If we're showing both text and icon, add some margin bottom to the icon
+ bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON);
+ }
+ if (bottomMargin != lp.bottomMargin) {
+ lp.bottomMargin = bottomMargin;
+ iconView.requestLayout();
+ }
+ }
+
if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
setOnLongClickListener(this);
} else {
@@ -1315,6 +1408,13 @@
public Tab getTab() {
return mTab;
}
+
+ /**
+ * Approximates a given lines width with the new provided text size.
+ */
+ private float approximateLineWidth(Layout layout, int line, float textSize) {
+ return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize());
+ }
}
private class SlidingTabStrip extends LinearLayout {
@@ -1327,6 +1427,8 @@
private int mIndicatorLeft = -1;
private int mIndicatorRight = -1;
+ private ValueAnimatorCompat mCurrentAnimator;
+
SlidingTabStrip(Context context) {
super(context);
setWillNotDraw(false);
@@ -1380,14 +1482,13 @@
if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
final int count = getChildCount();
- final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-
- // First we'll find the largest tab
+ // First we'll find the widest tab
int largestTabWidth = 0;
for (int i = 0, z = count; i < z; i++) {
- final View child = getChildAt(i);
- child.measure(unspecifiedSpec, heightMeasureSpec);
- largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
+ View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
+ }
}
if (largestTabWidth <= 0) {
@@ -1396,32 +1497,50 @@
}
final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
+ boolean remeasure = false;
+
if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
// If the tabs fit within our width minus gutters, we will set all tabs to have
// the same width
for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- final LinearLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.width = largestTabWidth;
- lp.weight = 0;
+ final LinearLayout.LayoutParams lp =
+ (LayoutParams) getChildAt(i).getLayoutParams();
+ if (lp.width != largestTabWidth || lp.weight != 0) {
+ lp.width = largestTabWidth;
+ lp.weight = 0;
+ remeasure = true;
+ }
}
} else {
// If the tabs will wrap to be larger than the width minus gutters, we need
// to switch to GRAVITY_FILL
mTabGravity = GRAVITY_FILL;
- updateTabViewsLayoutParams();
+ updateTabViews(false);
+ remeasure = true;
}
- // Now re-measure after our changes
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (remeasure) {
+ // Now re-measure after our changes
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- // If we've been layed out, update the indicator position
- updateIndicatorPosition();
+
+ if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+ // If we're currently running an animation, lets cancel it and start a
+ // new animation with the remaining duration
+ mCurrentAnimator.cancel();
+ final long duration = mCurrentAnimator.getDuration();
+ animateIndicatorToPosition(mSelectedPosition,
+ Math.round((1f - mCurrentAnimator.getAnimatedFraction()) * duration));
+ } else {
+ // If we've been layed out, update the indicator position
+ updateIndicatorPosition();
+ }
}
private void updateIndicatorPosition() {
@@ -1518,6 +1637,7 @@
}
});
animator.start();
+ mCurrentAnimator = animator;
}
}
@@ -1550,14 +1670,29 @@
return new ColorStateList(states, colors);
}
- private ColorStateList loadTextColorFromTextAppearance(int textAppearanceResId) {
- TypedArray a = getContext().obtainStyledAttributes(textAppearanceResId,
- R.styleable.TextAppearance);
- try {
- return a.getColorStateList(R.styleable.TextAppearance_android_textColor);
- } finally {
- a.recycle();
+ private int getDefaultHeight() {
+ boolean hasIconAndText = false;
+ for (int i = 0, count = mTabs.size(); i < count; i++) {
+ Tab tab = mTabs.get(i);
+ if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) {
+ hasIconAndText = true;
+ break;
+ }
}
+ return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT;
+ }
+
+ private int getTabMinWidth() {
+ if (mRequestedTabMinWidth != INVALID_WIDTH) {
+ // If we have been given a min width, use it
+ return mRequestedTabMinWidth;
+ }
+ // Else, we'll use the default value
+ return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0;
+ }
+
+ private int getTabMaxWidth() {
+ return mTabMaxWidth;
}
/**
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 16ebc30..2926606 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -86,6 +86,8 @@
// Can't call through to super(Context, AttributeSet, int) since it doesn't exist on API 10
super(context, attrs);
+ ThemeUtils.checkAppCompatTheme(context);
+
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
@@ -96,7 +98,7 @@
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout);
- mHint = a.getText(R.styleable.TextInputLayout_android_hint);
+ setHint(a.getText(R.styleable.TextInputLayout_android_hint));
mHintAnimationEnabled = a.getBoolean(
R.styleable.TextInputLayout_hintAnimationEnabled, true);
@@ -214,6 +216,8 @@
private void updateLabelVisibility(boolean animate) {
boolean hasText = mEditText != null && !TextUtils.isEmpty(mEditText.getText());
boolean isFocused = arrayContains(getDrawableState(), android.R.attr.state_focused);
+ boolean isErrorShowing = !TextUtils.isEmpty(getError());
+
if (mDefaultTextColor != null && mFocusedTextColor != null) {
mCollapsingTextHelper.setExpandedTextColor(mDefaultTextColor.getDefaultColor());
@@ -222,7 +226,7 @@
: mDefaultTextColor.getDefaultColor());
}
- if (hasText || isFocused) {
+ if (hasText || isFocused || isErrorShowing) {
// We should be showing the label so do so if it isn't already
collapseHint(animate);
} else {
@@ -362,6 +366,9 @@
// Set the EditText's background tint to the error color
ViewCompat.setBackgroundTintList(mEditText,
ColorStateList.valueOf(mErrorView.getCurrentTextColor()));
+
+ updateLabelVisibility(true);
+
} else {
if (mErrorView.getVisibility() == VISIBLE) {
ViewCompat.animate(mErrorView)
@@ -372,6 +379,8 @@
@Override
public void onAnimationEnd(View view) {
view.setVisibility(INVISIBLE);
+
+ updateLabelVisibility(true);
}
}).start();
diff --git a/design/src/android/support/design/widget/ThemeUtils.java b/design/src/android/support/design/widget/ThemeUtils.java
new file mode 100644
index 0000000..327a44d
--- /dev/null
+++ b/design/src/android/support/design/widget/ThemeUtils.java
@@ -0,0 +1,38 @@
+/*
+ * 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.design.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.design.R;
+
+class ThemeUtils {
+
+ private static final int[] APPCOMPAT_CHECK_ATTRS = { R.attr.colorPrimary };
+
+ static void checkAppCompatTheme(Context context) {
+ TypedArray a = context.obtainStyledAttributes(APPCOMPAT_CHECK_ATTRS);
+ final boolean failed = !a.hasValue(0);
+ if (a != null) {
+ a.recycle();
+ }
+ if (failed) {
+ throw new IllegalArgumentException("You need to use a Theme.AppCompat theme "
+ + "(or descendant) with the design library.");
+ }
+ }
+}
diff --git a/design/src/android/support/design/widget/ViewOffsetHelper.java b/design/src/android/support/design/widget/ViewOffsetHelper.java
index 6bde40b..a76ca9a 100644
--- a/design/src/android/support/design/widget/ViewOffsetHelper.java
+++ b/design/src/android/support/design/widget/ViewOffsetHelper.java
@@ -67,8 +67,8 @@
private static void tickleInvalidationFlag(View view) {
final float x = ViewCompat.getTranslationX(view);
- ViewCompat.setTranslationX(view, x + 1);
- ViewCompat.setTranslationX(view, x);
+ ViewCompat.setTranslationY(view, x + 1);
+ ViewCompat.setTranslationY(view, x);
}
/**
diff --git a/local.properties b/local.properties
index 5b31c8a..8aea008 100644
--- a/local.properties
+++ b/local.properties
@@ -1 +1,11 @@
-android.dir=../../
\ No newline at end of file
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Fri Aug 21 16:47:26 KST 2015
+android.dir=../../
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index 0a9ca47..a9db906 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -16,16 +16,19 @@
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDirs = ['src']
- main.res.srcDirs = ['res']
+ main.res.srcDir 'res'
+ main.assets.srcDir 'assets'
+ main.resources.srcDir 'src'
+
+ // this moves src/instrumentTest to tests so all folders follow:
+ // tests/java, tests/res, tests/assets, ...
+ // This is a *reset* so it replaces the default paths
+ androidTest.setRoot('tests')
+ androidTest.java.srcDir 'tests/src'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
-
- lintOptions {
- // TODO: fix errors and reenable.
- abortOnError false
- }
}
diff --git a/settings.gradle b/settings.gradle
index 4c4ac52..d33c2ac 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -48,3 +48,6 @@
include ':support-customtabs'
project(':support-customtabs').projectDir = new File(rootDir, 'customtabs')
+
+include ':support-recommendation'
+project(':support-recommendation').projectDir = new File(rootDir, 'recommendation')
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index dd03d6a..57b54e0 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -40,6 +40,16 @@
# -----------------------------------------------------------------------
+# A helper sub-library that makes direct use of API 23.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-api23
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api23)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
# A helper sub-library that makes direct use of API 21.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v17-leanback-api21
@@ -79,6 +89,7 @@
LOCAL_SDK_VERSION := 17
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v17-leanback-kitkat android-support-v17-leanback-jbmr2 \
+ android-support-v17-leanback-api23 \
android-support-v17-leanback-api21 android-support-v17-leanback-common
LOCAL_JAVA_LIBRARIES := \
android-support-v4 \
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index fb2832d..840be78 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -189,6 +189,7 @@
method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
method public int onProvideTheme();
method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
@@ -379,6 +380,7 @@
method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
method public void displayCompletions(java.util.List<java.lang.String>);
+ method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
method public android.graphics.drawable.Drawable getBadgeDrawable();
method public android.content.Intent getRecognizerIntent();
method public java.lang.String getTitle();
@@ -405,6 +407,7 @@
method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
method public void displayCompletions(java.util.List<java.lang.String>);
+ method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
method public android.graphics.drawable.Drawable getBadgeDrawable();
method public android.content.Intent getRecognizerIntent();
method public java.lang.String getTitle();
@@ -426,7 +429,7 @@
method public abstract boolean onQueryTextSubmit(java.lang.String);
}
- public class VerticalGridFragment extends android.app.Fragment {
+ public class VerticalGridFragment extends android.support.v17.leanback.app.BrandedFragment {
ctor public VerticalGridFragment();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
@@ -438,7 +441,7 @@
method public void setSelectedPosition(int);
}
- public class VerticalGridSupportFragment extends android.support.v4.app.Fragment {
+ public class VerticalGridSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
ctor public VerticalGridSupportFragment();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
@@ -728,6 +731,8 @@
method public abstract void onFragmentExit(java.util.List<android.animation.Animator>);
method public abstract void onFragmentReenter(java.util.List<android.animation.Animator>);
method public abstract void onFragmentReturn(java.util.List<android.animation.Animator>);
+ method public abstract void onImeAppearing(java.util.List<android.animation.Animator>);
+ method public abstract void onImeDisappearing(java.util.List<android.animation.Animator>);
}
public class FullWidthDetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
@@ -805,6 +810,8 @@
method public void onFragmentExit(java.util.List<android.animation.Animator>);
method public void onFragmentReenter(java.util.List<android.animation.Animator>);
method public void onFragmentReturn(java.util.List<android.animation.Animator>);
+ method public void onImeAppearing(java.util.List<android.animation.Animator>);
+ method public void onImeDisappearing(java.util.List<android.animation.Animator>);
method public int onProvideLayoutId();
}
@@ -825,9 +832,11 @@
method public boolean hasNext();
method public boolean infoOnly();
method public boolean isChecked();
+ method public boolean isEditable();
method public boolean isEnabled();
method public void setChecked(boolean);
method public void setEnabled(boolean);
+ method public void setTitle(java.lang.CharSequence);
field public static final int DEFAULT_CHECK_SET_ID = 1; // 0x1
field public static final int NO_CHECK_SET = 0; // 0x0
field public static final int NO_DRAWABLE = 0; // 0x0
@@ -839,6 +848,7 @@
method public android.support.v17.leanback.widget.GuidedAction.Builder checkSetId(int);
method public android.support.v17.leanback.widget.GuidedAction.Builder checked(boolean);
method public android.support.v17.leanback.widget.GuidedAction.Builder description(java.lang.String);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder editable(boolean);
method public android.support.v17.leanback.widget.GuidedAction.Builder enabled(boolean);
method public android.support.v17.leanback.widget.GuidedAction.Builder hasNext(boolean);
method public android.support.v17.leanback.widget.GuidedAction.Builder icon(android.graphics.drawable.Drawable);
@@ -850,6 +860,13 @@
method public android.support.v17.leanback.widget.GuidedAction.Builder title(java.lang.String);
}
+ public class GuidedActionEditText extends android.widget.EditText implements android.support.v17.leanback.widget.ImeKeyMonitor {
+ ctor public GuidedActionEditText(android.content.Context);
+ ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet);
+ ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet, int);
+ method public void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
+ }
+
public class GuidedActionsStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
ctor public GuidedActionsStylist();
method public android.support.v17.leanback.widget.VerticalGridView getActionsGridView();
@@ -865,6 +882,8 @@
method public void onFragmentExit(java.util.List<android.animation.Animator>);
method public void onFragmentReenter(java.util.List<android.animation.Animator>);
method public void onFragmentReturn(java.util.List<android.animation.Animator>);
+ method public void onImeAppearing(java.util.List<android.animation.Animator>);
+ method public void onImeDisappearing(java.util.List<android.animation.Animator>);
method public int onProvideItemLayoutId();
method public int onProvideLayoutId();
field protected android.support.v17.leanback.widget.VerticalGridView mActionsGridView;
@@ -878,6 +897,7 @@
method public android.widget.ImageView getChevronView();
method public android.view.View getContentView();
method public android.widget.TextView getDescriptionView();
+ method public android.widget.EditText getEditableTitleView();
method public android.widget.ImageView getIconView();
method public android.widget.TextView getTitleView();
field public final android.view.View view;
@@ -918,9 +938,10 @@
}
public class ImageCardView extends android.support.v17.leanback.widget.BaseCardView {
+ ctor public ImageCardView(android.content.Context, int);
+ ctor public ImageCardView(android.content.Context, android.util.AttributeSet, int);
ctor public ImageCardView(android.content.Context);
ctor public ImageCardView(android.content.Context, android.util.AttributeSet);
- ctor public ImageCardView(android.content.Context, android.util.AttributeSet, int);
method public android.graphics.drawable.Drawable getBadgeImage();
method public java.lang.CharSequence getContentText();
method public android.graphics.drawable.Drawable getInfoAreaBackground();
@@ -937,6 +958,19 @@
method public void setMainImageDimensions(int, int);
method public void setMainImageScaleType(android.widget.ImageView.ScaleType);
method public void setTitleText(java.lang.CharSequence);
+ field public static final int CARD_TYPE_FLAG_CONTENT = 2; // 0x2
+ field public static final int CARD_TYPE_FLAG_ICON_LEFT = 8; // 0x8
+ field public static final int CARD_TYPE_FLAG_ICON_RIGHT = 4; // 0x4
+ field public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0; // 0x0
+ field public static final int CARD_TYPE_FLAG_TITLE = 1; // 0x1
+ }
+
+ public abstract interface ImeKeyMonitor {
+ method public abstract void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
+ }
+
+ public static abstract interface ImeKeyMonitor.ImeKeyListener {
+ method public abstract boolean onKeyPreIme(android.widget.EditText, int, android.view.KeyEvent);
}
public final class ItemAlignmentFacet {
@@ -1012,6 +1046,12 @@
method public abstract void wrap(android.view.View, android.view.View);
}
+ public class ItemBridgeAdapterShadowOverlayWrapper extends android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper {
+ ctor public ItemBridgeAdapterShadowOverlayWrapper(android.support.v17.leanback.widget.ShadowOverlayHelper);
+ method public android.view.View createWrapper(android.view.View);
+ method public void wrap(android.view.View, android.view.View);
+ }
+
public class ListRow extends android.support.v17.leanback.widget.Row {
ctor public ListRow(android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
ctor public ListRow(long, android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
@@ -1035,6 +1075,7 @@
ctor public ListRowPresenter(int, boolean);
method public final boolean areChildRoundedCornersEnabled();
method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
method public final void enableChildRoundedCorners(boolean);
method public int getExpandedRowHeight();
method public final int getFocusZoomFactor();
@@ -1044,12 +1085,14 @@
method public final boolean getShadowEnabled();
method public final deprecated int getZoomFactor();
method public final boolean isFocusDimmerUsed();
+ method public final boolean isKeepChildForeground();
method public boolean isUsingDefaultListSelectEffect();
method public final boolean isUsingDefaultSelectEffect();
method public boolean isUsingDefaultShadow();
method public boolean isUsingZOrder(android.content.Context);
method public void setExpandedRowHeight(int);
method public final void setHoverCardPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public final void setKeepChildForeground(boolean);
method public void setRecycledPoolSize(android.support.v17.leanback.widget.Presenter, int);
method public void setRowHeight(int);
method public final void setShadowEnabled(boolean);
@@ -1327,7 +1370,6 @@
public abstract class RowPresenter extends android.support.v17.leanback.widget.Presenter {
ctor public RowPresenter();
- method public boolean canDrawOutOfBounds();
method protected abstract android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
method protected void dispatchItemSelectedListener(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
method public void freeze(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
@@ -1387,6 +1429,7 @@
ctor public SearchBar(android.content.Context, android.util.AttributeSet);
ctor public SearchBar(android.content.Context, android.util.AttributeSet, int);
method public void displayCompletions(java.util.List<java.lang.String>);
+ method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
method public android.graphics.drawable.Drawable getBadgeDrawable();
method public java.lang.CharSequence getHint();
method public java.lang.String getTitle();
@@ -1443,15 +1486,14 @@
field public int iconColor;
}
- public class ShadowOverlayContainer extends android.view.ViewGroup {
+ public class ShadowOverlayContainer extends android.widget.FrameLayout {
ctor public ShadowOverlayContainer(android.content.Context);
ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet);
ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet, int);
method public int getShadowType();
method public android.view.View getWrappedView();
method public deprecated void initialize(boolean, boolean);
- method public void initialize(boolean, boolean, boolean);
- method protected void onLayout(boolean, int, int, int, int);
+ method public deprecated void initialize(boolean, boolean, boolean);
method public static void prepareParentForShadow(android.view.ViewGroup);
method public void setOverlayColor(int);
method public void setShadowFocusLevel(float);
@@ -1466,6 +1508,48 @@
field public static final int SHADOW_STATIC = 2; // 0x2
}
+ public final class ShadowOverlayHelper {
+ method public android.support.v17.leanback.widget.ShadowOverlayContainer createShadowOverlayContainer(android.content.Context);
+ method public int getShadowType();
+ method public boolean needsOverlay();
+ method public boolean needsRoundedCorner();
+ method public boolean needsWrapper();
+ method public void onViewCreated(android.view.View);
+ method public void prepareParentForShadow(android.view.ViewGroup);
+ method public static void setNoneWrapperOverlayColor(android.view.View, int);
+ method public static void setNoneWrapperShadowFocusLevel(android.view.View, float);
+ method public void setOverlayColor(android.view.View, int);
+ method public void setShadowFocusLevel(android.view.View, float);
+ method public static boolean supportsDynamicShadow();
+ method public static boolean supportsForeground();
+ method public static boolean supportsRoundedCorner();
+ method public static boolean supportsShadow();
+ field public static final int SHADOW_DYNAMIC = 3; // 0x3
+ field public static final int SHADOW_NONE = 1; // 0x1
+ field public static final int SHADOW_STATIC = 2; // 0x2
+ }
+
+ public static final class ShadowOverlayHelper.Builder {
+ ctor public ShadowOverlayHelper.Builder();
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper build(android.content.Context);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder keepForegroundDrawable(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsOverlay(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsRoundedCorner(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsShadow(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder options(android.support.v17.leanback.widget.ShadowOverlayHelper.Options);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder preferZOrder(boolean);
+ }
+
+ public static final class ShadowOverlayHelper.Options {
+ ctor public ShadowOverlayHelper.Options();
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options dynamicShadowZ(float, float);
+ method public final float getDynamicShadowFocusedZ();
+ method public final float getDynamicShadowUnfocusedZ();
+ method public final int getRoundedCornerRadius();
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options roundedCornerRadius(int);
+ field public static final android.support.v17.leanback.widget.ShadowOverlayHelper.Options DEFAULT;
+ }
+
public final class SinglePresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
ctor public SinglePresenterSelector(android.support.v17.leanback.widget.Presenter);
method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
@@ -1538,8 +1622,10 @@
ctor public VerticalGridPresenter(int, boolean);
method public final boolean areChildRoundedCornersEnabled();
method protected android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder createGridViewHolder(android.view.ViewGroup);
+ method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
method public final void enableChildRoundedCorners(boolean);
method public final int getFocusZoomFactor();
+ method public final boolean getKeepChildForeground();
method public int getNumberOfColumns();
method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
@@ -1551,6 +1637,8 @@
method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
method public final android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void setEntranceTransitionState(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder, boolean);
+ method public final void setKeepChildForeground(boolean);
method public void setNumberOfColumns(int);
method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java
index c7fa0f3..a013ba1 100644
--- a/v17/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java
@@ -13,31 +13,47 @@
*/
package android.support.v17.leanback.widget;
-import android.support.v17.leanback.R;
-import android.graphics.Color;
+import android.util.SparseArray;
import android.graphics.Outline;
-import android.graphics.drawable.GradientDrawable;
import android.view.ViewOutlineProvider;
import android.view.View;
class RoundedRectHelperApi21 {
- private static int sCornerRadius;
+ private static SparseArray<ViewOutlineProvider> sRoundedRectProvider;
+ private static final int MAX_CACHED_PROVIDER = 32;
- private static final ViewOutlineProvider sOutlineProvider = new ViewOutlineProvider() {
+ static final class RoundedRectOutlineProvider extends ViewOutlineProvider {
+
+ private int mRadius;
+
+ RoundedRectOutlineProvider(int radius) {
+ mRadius = radius;
+ }
+
@Override
public void getOutline(View view, Outline outline) {
- if (sCornerRadius == 0) {
- sCornerRadius = view.getResources().getDimensionPixelSize(
- R.dimen.lb_rounded_rect_corner_radius);
- }
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), sCornerRadius);
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mRadius);
outline.setAlpha(1f);
}
};
- public static void setClipToRoundedOutline(View view, boolean clip) {
- view.setOutlineProvider(clip ? sOutlineProvider : ViewOutlineProvider.BACKGROUND);
+ public static void setClipToRoundedOutline(View view, boolean clip, int roundedCornerRadius) {
+ if (clip) {
+ if (sRoundedRectProvider == null) {
+ sRoundedRectProvider = new SparseArray<ViewOutlineProvider>();
+ }
+ ViewOutlineProvider provider = sRoundedRectProvider.get(roundedCornerRadius);
+ if (provider == null) {
+ provider = new RoundedRectOutlineProvider(roundedCornerRadius);
+ if (sRoundedRectProvider.size() < MAX_CACHED_PROVIDER) {
+ sRoundedRectProvider.put(roundedCornerRadius, provider);
+ }
+ }
+ view.setOutlineProvider(provider);
+ } else {
+ view.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+ }
view.setClipToOutline(clip);
}
}
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
index ab4d179..6e491fd 100644
--- a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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
@@ -19,14 +19,14 @@
import android.graphics.Outline;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.view.ViewGroup;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
class ShadowHelperApi21 {
static class ShadowImpl {
- ViewGroup mShadowContainer;
+ View mShadowContainer;
float mNormalZ;
float mFocusedZ;
}
@@ -41,9 +41,10 @@
/* add shadows and return a implementation detail object */
public static Object addDynamicShadow(
- ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
- if (roundedCorners) {
- RoundedRectHelperApi21.setClipToRoundedOutline(shadowContainer, true);
+ View shadowContainer, float unfocusedZ, float focusedZ, int roundedCornerRadius) {
+ if (roundedCornerRadius > 0) {
+ RoundedRectHelperApi21.setClipToRoundedOutline(shadowContainer, true,
+ roundedCornerRadius);
} else {
shadowContainer.setOutlineProvider(sOutlineProvider);
}
@@ -52,7 +53,9 @@
impl.mNormalZ = unfocusedZ;
impl.mFocusedZ = focusedZ;
shadowContainer.setZ(impl.mNormalZ);
- shadowContainer.setTransitionGroup(true);
+ if (shadowContainer instanceof ViewGroup) {
+ ((ViewGroup) shadowContainer).setTransitionGroup(true);
+ }
return impl;
}
diff --git a/v17/leanback/api23/android/support/v17/leanback/widget/ForegroundHelperApi23.java b/v17/leanback/api23/android/support/v17/leanback/widget/ForegroundHelperApi23.java
new file mode 100644
index 0000000..c4760d4
--- /dev/null
+++ b/v17/leanback/api23/android/support/v17/leanback/widget/ForegroundHelperApi23.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+class ForegroundHelperApi23 {
+
+ public static Drawable getForeground(View view) {
+ return view.getForeground();
+ }
+
+ public static void setForeground(View view, Drawable drawable) {
+ view.setForeground(drawable);
+ }
+}
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 8dc79e8..401a5c4 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -19,8 +19,8 @@
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
- main.aidl.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
+ main.java.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'api23', 'src']
+ main.aidl.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'api23', 'src']
main.res.srcDirs = ['res']
androidTest.setRoot('tests')
diff --git a/v7/mediarouter/res/values-sw600dp/dimens.xml b/v17/leanback/res/animator/lb_guidedstep_slide_down.xml
similarity index 62%
copy from v7/mediarouter/res/values-sw600dp/dimens.xml
copy to v17/leanback/res/animator/lb_guidedstep_slide_down.xml
index 5b29058..b31421f 100644
--- a/v7/mediarouter/res/values-sw600dp/dimens.xml
+++ b/v17/leanback/res/animator/lb_guidedstep_slide_down.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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.
@@ -13,7 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
- <dimen name="mr_media_route_controller_art_max_height">480dp</dimen>
-</resources>
\ No newline at end of file
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@android:integer/config_shortAnimTime"
+ android:propertyName="translationY"
+ android:valueFrom="@dimen/lb_guidedstep_slide_ime_distance"
+ android:valueTo="0.0"
+ android:valueType="floatType" />
diff --git a/v7/mediarouter/res/values-sw600dp/dimens.xml b/v17/leanback/res/animator/lb_guidedstep_slide_up.xml
similarity index 62%
copy from v7/mediarouter/res/values-sw600dp/dimens.xml
copy to v17/leanback/res/animator/lb_guidedstep_slide_up.xml
index 5b29058..165fe18 100644
--- a/v7/mediarouter/res/values-sw600dp/dimens.xml
+++ b/v17/leanback/res/animator/lb_guidedstep_slide_up.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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.
@@ -13,7 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<resources>
- <dimen name="mr_media_route_controller_art_max_height">480dp</dimen>
-</resources>
\ No newline at end of file
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@android:integer/config_shortAnimTime"
+ android:propertyName="translationY"
+ android:valueFrom="0.0"
+ android:valueTo="@dimen/lb_guidedstep_slide_ime_distance"
+ android:valueType="floatType" />
diff --git a/v17/leanback/res/layout/lb_guidedactions_item.xml b/v17/leanback/res/layout/lb_guidedactions_item.xml
index 4e41454..3babaa9 100644
--- a/v17/leanback/res/layout/lb_guidedactions_item.xml
+++ b/v17/leanback/res/layout/lb_guidedactions_item.xml
@@ -34,7 +34,7 @@
android:id="@+id/guidedactions_item_content"
style="?attr/guidedActionItemContentStyle" >
- <TextView
+ <android.support.v17.leanback.widget.GuidedActionEditText
android:id="@+id/guidedactions_item_title"
style="?attr/guidedActionItemTitleStyle" />
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
index 2261965..1bc23f8 100644
--- a/v17/leanback/res/layout/lb_image_card_view.xml
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -15,59 +15,16 @@
limitations under the License.
-->
-<merge
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:lb="http://schemas.android.com/apk/res-auto">
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto" >
<ImageView
android:id="@+id/main_image"
- lb:layout_viewType="main"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scaleType="centerCrop"
- android:adjustViewBounds="true"
- android:contentDescription="@null" />
- <android.support.v17.leanback.widget.NonOverlappingRelativeLayout
- lb:layout_viewType="info"
- android:id="@+id/info_field"
- android:layout_width="match_parent"
- android:layout_height="@dimen/lb_basic_card_info_height"
- android:paddingStart="@dimen/lb_basic_card_info_padding_horizontal"
- android:paddingEnd="@dimen/lb_basic_card_info_padding_horizontal"
- android:paddingTop="@dimen/lb_basic_card_info_padding_top"
- android:layout_centerHorizontal="true" >
- <TextView
- android:id="@+id/title_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_marginBottom="@dimen/lb_basic_card_info_text_margin"
- android:maxLines="1"
- android:fontFamily="sans-serif-condensed"
- android:textColor="@color/lb_basic_card_title_text_color"
- android:textSize="@dimen/lb_basic_card_title_text_size"
- android:ellipsize="end" />
- <TextView
- android:id="@+id/content_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/title_text"
- android:layout_alignParentStart="true"
- android:layout_toStartOf="@+id/extra_badge"
- android:maxLines="1"
- android:fontFamily="sans-serif-condensed"
- android:textColor="@color/lb_basic_card_content_text_color"
- android:textSize="@dimen/lb_basic_card_content_text_size"
- android:ellipsize="none" />
- <ImageView
- android:id="@+id/extra_badge"
- android:layout_width="@dimen/lb_basic_card_info_badge_size"
- android:layout_height="@dimen/lb_basic_card_info_badge_size"
- android:layout_marginStart="@dimen/lb_basic_card_info_badge_margin"
- android:layout_alignBottom="@id/content_text"
- android:layout_alignParentEnd="true"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@null" />
- </android.support.v17.leanback.widget.NonOverlappingRelativeLayout>
-</merge>
+ style="?attr/lbImageCardViewImageStyle" />
+
+ <android.support.v17.leanback.widget.NonOverlappingRelativeLayout
+ android:id="@+id/info_field"
+ style="?attr/lbImageCardViewInfoAreaStyle">
+ </android.support.v17.leanback.widget.NonOverlappingRelativeLayout>
+
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
similarity index 68%
copy from v17/leanback/res/layout/lb_card_color_overlay.xml
copy to v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
index 45a40e1..35d2da6 100644
--- a/v17/leanback/res/layout/lb_card_color_overlay.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
@@ -15,7 +15,9 @@
limitations under the License.
-->
-<View
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/extra_badge"
+ android:layout_alignBottom="@+id/content_text"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/lb_basic_card_info_badge_margin"
+ style="?attr/lbImageCardViewBadgeStyle" />
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
similarity index 68%
copy from v17/leanback/res/layout/lb_card_color_overlay.xml
copy to v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
index 45a40e1..02dd917 100644
--- a/v17/leanback/res/layout/lb_card_color_overlay.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
@@ -15,7 +15,9 @@
limitations under the License.
-->
-<View
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/extra_badge"
+ android:layout_alignBottom="@+id/content_text"
+ android:layout_alignParentEnd="true"
+ android:layout_marginStart="@dimen/lb_basic_card_info_badge_margin"
+ style="?attr/lbImageCardViewBadgeStyle" />
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_image_card_view_themed_content.xml
similarity index 80%
rename from v17/leanback/res/layout/lb_card_color_overlay.xml
rename to v17/leanback/res/layout/lb_image_card_view_themed_content.xml
index 45a40e1..5592371 100644
--- a/v17/leanback/res/layout/lb_card_color_overlay.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_content.xml
@@ -15,7 +15,6 @@
limitations under the License.
-->
-<View
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_text"
+ style="?attr/lbImageCardViewContentStyle" />
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_image_card_view_themed_title.xml
similarity index 80%
copy from v17/leanback/res/layout/lb_card_color_overlay.xml
copy to v17/leanback/res/layout/lb_image_card_view_themed_title.xml
index 45a40e1..67e2493 100644
--- a/v17/leanback/res/layout/lb_card_color_overlay.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_title.xml
@@ -15,7 +15,6 @@
limitations under the License.
-->
-<View
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/title_text"
+ style="?attr/lbImageCardViewTitleStyle" />
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml
similarity index 70%
copy from v17/leanback/res/layout/lb_card_color_overlay.xml
copy to v17/leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml
index 45a40e1..00466cb 100644
--- a/v17/leanback/res/layout/lb_card_color_overlay.xml
+++ b/v17/leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2014 The Android Open Source Project
+ 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.
@@ -15,7 +15,8 @@
limitations under the License.
-->
-<View
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <fade
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:duration="150"/>
+</transitionSet>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values-sw600dp/dimens.xml b/v17/leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml
similarity index 66%
copy from v7/mediarouter/res/values-sw600dp/dimens.xml
copy to v17/leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml
index 5b29058..ee06953 100644
--- a/v7/mediarouter/res/values-sw600dp/dimens.xml
+++ b/v17/leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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.
@@ -14,6 +15,10 @@
limitations under the License.
-->
-<resources>
- <dimen name="mr_media_route_controller_art_max_height">480dp</dimen>
-</resources>
\ No newline at end of file
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <slide
+ android:duration="350"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:slideEdge="bottom">
+ </slide>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_vertical_grid_return_transition.xml b/v17/leanback/res/transition-v21/lb_vertical_grid_return_transition.xml
new file mode 100644
index 0000000..edb3816
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_vertical_grid_return_transition.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+ <slide
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350"
+ android:slideEdge="bottom">
+ <targets>
+ <target android:excludeId="@+id/title_orb" />
+ <target android:excludeId="@+id/title_text" />
+ <target android:excludeId="@+id/title_badge" />
+ </targets>
+ </slide>
+ <fade
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="350">
+ </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/values-az-rAZ/strings.xml b/v17/leanback/res/values-az-rAZ/strings.xml
deleted file mode 100644
index d1e685f..0000000
--- a/v17/leanback/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,49 +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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="orb_search_action" msgid="5651268540267663887">"Axtarış Fəaliyyəti"</string>
- <string name="lb_search_bar_hint" msgid="8325490927970116252">"Axtarış"</string>
- <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Axtarış üçün danışın"</string>
- <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Axtarış: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Axtarış üçün danışın: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
- <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
- <string name="lb_playback_controls_play" msgid="731953341987346903">"Oyun"</string>
- <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauza"</string>
- <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"İrəli Ötürmə"</string>
- <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"İrəli sarı %1$dX"</string>
- <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Geri ötürmə"</string>
- <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Geri sarı %1$dX"</string>
- <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Növbətini atlayın"</string>
- <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Öncəkini atlayın"</string>
- <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Digər fəaliyyətlər"</string>
- <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Bəyənməkdən imtina edin"</string>
- <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Bəyənin"</string>
- <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Bəyənməməkdən imtina edin"</string>
- <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Bəyənməyin"</string>
- <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Təkrarlanmasın"</string>
- <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Hamısını təkrarlayın"</string>
- <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Biri təkrarlansın"</string>
- <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Qarışdırma aktiv edilsin"</string>
- <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Qarışdırma deaktiv edilsin"</string>
- <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Yüksək keyfiyyəti aktiv edin"</string>
- <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksək keyfiyyəti deaktiv edin"</string>
- <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Qapalı çəkilişi aktiv edin"</string>
- <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Qapalı çəkilişi deaktiv edin"</string>
-</resources>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index e7da321..10ee282 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -49,6 +49,10 @@
</declare-styleable>
<declare-styleable name="lbBaseCardView">
+ <!-- Defines the background of card -->
+ <attr name="cardForeground" format="reference|color"/>
+ <!-- Defines the background of card -->
+ <attr name="cardBackground" format="reference|color"/>
<!-- Defines the type of the card layout -->
<attr name="cardType" format="enum">
<!-- A simple card layout with a single layout region. -->
@@ -116,7 +120,23 @@
</declare-styleable>
<declare-styleable name="lbImageCardView">
+ <!-- Deprecated. Use 'lbImageCardViewInfoAreaStyle' instead. -->
<attr name="infoAreaBackground" format="reference|color"/>
+ <!-- Use these attributes to override a ImageCardView's component style. -->
+ <attr name="lbImageCardViewImageStyle" format="reference" />
+ <attr name="lbImageCardViewTitleStyle" format="reference" />
+ <attr name="lbImageCardViewContentStyle" format="reference" />
+ <attr name="lbImageCardViewBadgeStyle" format="reference" />
+ <attr name="lbImageCardViewInfoAreaStyle" format="reference" />
+ <!-- Defines what components the ImageCardView will use. -->
+ <attr name="lbImageCardViewType">
+ <flag name="Title" value="1" />
+ <flag name="Content" value="2" />
+ <flag name="IconOnRight" value="4" />
+ <flag name="IconOnLeft" value="8" />
+ <!-- Only display the main image. -->
+ <flag name="ImageOnly" value="0" />
+ </attr>
</declare-styleable>
<declare-styleable name="lbSearchOrbView">
@@ -308,6 +328,14 @@
fragment stack pop. Default is {@link
android.support.v17.leanback.R.animator#lb_guidedstep_slide_out_to_end}. -->
<attr name="guidedStepReturnAnimation" format="reference" />
+ <!-- Theme attribute for the animation used when a guided step element is animated in
+ response to the IME appearing. Default is {@link
+ android.support.v17.leanback.R.animator#lb_guidedstep_slide_up}. -->
+ <attr name="guidedStepImeAppearingAnimation" format="reference" />
+ <!-- Theme attribute for the animation used when a guided step element is animated in
+ response to the IME disappearing. Default is {@link
+ android.support.v17.leanback.R.animator#lb_guidedstep_slide_down}. -->
+ <attr name="guidedStepImeDisappearingAnimation" format="reference" />
<!-- Theme attribute for the animation used when the guidance is animated in at activity
start. Default is {@link android.support.v17.leanback.R.animator#lb_guidance_entry}.
@@ -431,4 +459,4 @@
</declare-styleable>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 275612e..a05a6fc 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -203,6 +203,7 @@
<dimen name="lb_basic_card_info_height">52dp</dimen>
<dimen name="lb_basic_card_info_height_no_content">34dp</dimen>
<dimen name="lb_basic_card_info_padding_top">7dp</dimen>
+ <dimen name="lb_basic_card_info_padding_bottom">8dp</dimen>
<dimen name="lb_basic_card_info_padding_horizontal">11dp</dimen>
<dimen name="lb_basic_card_info_text_margin">1dp</dimen>
<dimen name="lb_basic_card_title_text_size">14sp</dimen>
@@ -226,6 +227,7 @@
<dimen name="lb_guidedstep_guidance_section_width">576dp</dimen>
<dimen name="lb_guidedstep_slide_start_distance">-200dp</dimen>
<dimen name="lb_guidedstep_slide_end_distance">200dp</dimen>
+ <dimen name="lb_guidedstep_slide_ime_distance">-100dp</dimen>
<dimen name="lb_guidance_entry_translationX">-120dp</dimen>
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
index 6b67919..d4ba288 100644
--- a/v17/leanback/res/values/ids.xml
+++ b/v17/leanback/res/values/ids.xml
@@ -16,6 +16,7 @@
-->
<resources>
<item type="id" name="lb_focus_animator" />
+ <item type="id" name="lb_shadow_impl" />
<item type="id" name="lb_slide_transition_value" />
<item type="id" name="lb_control_play_pause" />
@@ -30,5 +31,4 @@
<item type="id" name="lb_control_shuffle" />
<item type="id" name="lb_control_high_quality" />
<item type="id" name="lb_control_closed_captioning" />
-
</resources>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 3ee2821..204c0ee 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -87,14 +87,8 @@
<style name="Widget.Leanback" parent="Widget.LeanbackBase" />
<style name="Widget.Leanback.BaseCardViewStyle">
- <item name="android:foreground">@drawable/lb_card_foreground</item>
- </style>
-
- <style name="Widget.Leanback.ImageCardViewStyle" parent="Widget.Leanback.BaseCardViewStyle">
- <item name="cardType">infoUnder</item>
- <item name="infoVisibility">activated</item>
- <item name="android:background">@color/lb_basic_card_bg_color</item>
- <item name="infoAreaBackground">@color/lb_basic_card_info_bg_color</item>
+ <item name="cardForeground">@drawable/lb_card_foreground</item>
+ <item name="cardBackground">@color/lb_basic_card_bg_color</item>
</style>
<style name="Widget.Leanback.TitleView" >
@@ -104,6 +98,77 @@
<item name="android:paddingEnd">?attr/browsePaddingEnd</item>
</style>
+ <style name="Widget.Leanback.ImageCardViewStyle" parent="Widget.Leanback.BaseCardViewStyle">
+ <item name="cardType">infoUnder</item>
+ <item name="infoVisibility">activated</item>
+ <!-- In order to keep backward compatibility we have to create an icon on right. -->
+ <item name="lbImageCardViewType">Title|Content|IconOnRight</item>
+ <item name="lbImageCardViewImageStyle">@style/Widget.Leanback.ImageCardView.ImageStyle</item>
+ <item name="lbImageCardViewTitleStyle">@style/Widget.Leanback.ImageCardView.TitleStyle</item>
+ <item name="lbImageCardViewContentStyle">@style/Widget.Leanback.ImageCardView.ContentStyle</item>
+ <item name="lbImageCardViewBadgeStyle">@style/Widget.Leanback.ImageCardView.BadgeStyle</item>
+ <item name="lbImageCardViewInfoAreaStyle">@style/Widget.Leanback.ImageCardView.InfoAreaStyle</item>
+ <!-- Deprecated. Use 'Widget.Leanback.ImageCardView.InfoAreaStyle' instead. -->
+ <item name="infoAreaBackground">@null</item>
+ </style>
+
+ <style name="Widget.Leanback.ImageCardView" />
+
+ <style name="Widget.Leanback.ImageCardView.ImageStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:adjustViewBounds">true</item>
+ <item name="android:contentDescription">@null</item>
+ <item name="android:scaleType">centerCrop</item>
+ <item name="layout_viewType">main</item>
+ </style>
+
+ <style name="Widget.Leanback.ImageCardView.InfoAreaStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_centerHorizontal">true</item>
+ <item name="layout_viewType">info</item>
+ <item name="android:paddingBottom">@dimen/lb_basic_card_info_padding_bottom</item>
+ <item name="android:paddingEnd">@dimen/lb_basic_card_info_padding_horizontal</item>
+ <item name="android:paddingStart">@dimen/lb_basic_card_info_padding_horizontal</item>
+ <item name="android:paddingTop">@dimen/lb_basic_card_info_padding_top</item>
+ <item name="android:background">@color/lb_basic_card_info_bg_color</item>
+ </style>
+
+ <style name="Widget.Leanback.ImageCardView.TitleStyle">
+ <item name="android:id">@id/title_text</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:layout_marginBottom">@dimen/lb_basic_card_info_text_margin</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
+ <item name="android:textColor">@color/lb_basic_card_title_text_color</item>
+ <item name="android:textSize">@dimen/lb_basic_card_title_text_size</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="Widget.Leanback.ImageCardView.ContentStyle">
+ <item name="android:id">@id/content_text</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_alignParentStart">true</item>
+ <item name="android:layout_below">@+id/title_text</item>
+ <item name="android:layout_toStartOf">@+id/extra_badge</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
+ <item name="android:textColor">@color/lb_basic_card_content_text_color</item>
+ <item name="android:textSize">@dimen/lb_basic_card_content_text_size</item>
+ <item name="android:ellipsize">none</item>
+ </style>
+
+ <style name="Widget.Leanback.ImageCardView.BadgeStyle">
+ <item name="android:id">@id/extra_badge</item>
+ <item name="android:layout_width">@dimen/lb_basic_card_info_badge_size</item>
+ <item name="android:layout_height">@dimen/lb_basic_card_info_badge_size</item>
+ <item name="android:contentDescription">@null</item>
+ <item name="android:scaleType">fitCenter</item>
+ </style>
+
<style name="Widget.Leanback.Title" />
<style name="Widget.Leanback.Title.Text">
@@ -430,11 +495,13 @@
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:alpha">@string/lb_guidedactions_item_unselected_text_alpha</item>
- <item name="android:ellipsize">marquee</item>
+ <item name="android:ellipsize">end</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:maxLines">@integer/lb_guidedactions_item_title_min_lines</item>
<item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
<item name="android:textSize">@dimen/lb_guidedactions_item_title_font_size</item>
+ <item name="android:background">@null</item>
+ <item name="android:inputType">text</item>
</style>
<!-- Style for an action's description in a GuidedActionsStylist's default item layout. -->
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 8178c50..d994b3f 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -95,6 +95,11 @@
<item name="android:windowReturnTransition">@transition/lb_browse_return_transition</item>
</style>
+ <style name="Theme.Leanback.VerticalGrid" parent="Theme.Leanback">
+ <item name="android:windowEnterTransition">@transition/lb_vertical_grid_enter_transition</item>
+ <item name="android:windowReturnTransition">@transition/lb_vertical_grid_return_transition</item>
+ </style>
+
<style name="Theme.Leanback.Details" parent="Theme.Leanback">
<item name="android:windowEnterTransition">@transition/lb_details_enter_transition</item>
<item name="android:windowReturnTransition">@transition/lb_details_return_transition</item>
@@ -115,6 +120,8 @@
<item name="guidedStepExitAnimation">@animator/lb_guidedstep_slide_out_to_start</item>
<item name="guidedStepReentryAnimation">@animator/lb_guidedstep_slide_in_from_start</item>
<item name="guidedStepReturnAnimation">@animator/lb_guidedstep_slide_out_to_end</item>
+ <item name="guidedStepImeAppearingAnimation">@animator/lb_guidedstep_slide_up</item>
+ <item name="guidedStepImeDisappearingAnimation">@animator/lb_guidedstep_slide_down</item>
<item name="guidanceEntryAnimation">@animator/lb_guidance_entry</item>
<item name="guidedActionsEntryAnimation">@animator/lb_guidedactions_entry</item>
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 d415de0..c786040 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -394,11 +394,7 @@
super.onStart();
setupChildFragmentLayout();
setupFocusSearchListener();
- mRowsFragment.getView().requestFocus();
if (isEntranceTransitionEnabled()) {
- // make sure recycler view animation is disabled
- mRowsFragment.onTransitionPrepare();
- mRowsFragment.onTransitionStart();
mRowsFragment.setEntranceTransitionState(false);
}
}
@@ -420,4 +416,13 @@
mRowsFragment.onTransitionEnd();
}
+ @Override
+ protected void onEntranceTransitionPrepare() {
+ mRowsFragment.onTransitionPrepare();
+ }
+
+ @Override
+ protected void onEntranceTransitionStart() {
+ mRowsFragment.onTransitionStart();
+ }
}
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 afaf5a8..fbce207 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -396,11 +396,7 @@
super.onStart();
setupChildFragmentLayout();
setupFocusSearchListener();
- mRowsSupportFragment.getView().requestFocus();
if (isEntranceTransitionEnabled()) {
- // make sure recycler view animation is disabled
- mRowsSupportFragment.onTransitionPrepare();
- mRowsSupportFragment.onTransitionStart();
mRowsSupportFragment.setEntranceTransitionState(false);
}
}
@@ -422,4 +418,13 @@
mRowsSupportFragment.onTransitionEnd();
}
+ @Override
+ protected void onEntranceTransitionPrepare() {
+ mRowsSupportFragment.onTransitionPrepare();
+ }
+
+ @Override
+ protected void onEntranceTransitionStart() {
+ mRowsSupportFragment.onTransitionStart();
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
index 41ecac9..975357e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
@@ -19,6 +19,7 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.GuidedAction;
import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.ImeKeyMonitor;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
@@ -26,9 +27,14 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
import java.util.ArrayList;
import java.util.List;
@@ -43,6 +49,9 @@
private static final String TAG = "GuidedActionAdapter";
private static final boolean DEBUG = false;
+ private static final String TAG_EDIT = "EditableAction";
+ private static final boolean DEBUG_EDIT = false;
+
/**
* Object listening for click events within a {@link GuidedActionAdapter}.
*/
@@ -66,6 +75,17 @@
}
/**
+ * Object listening for edit events within a {@link GuidedActionAdapter}.
+ */
+ public interface EditListener {
+
+ /**
+ * Called when the user enters or exits edit mode on an action.
+ */
+ public void onGuidedActionEdited(GuidedAction action, boolean entering);
+ }
+
+ /**
* View holder containing a {@link GuidedAction}.
*/
private static class ActionViewHolder extends ViewHolder {
@@ -101,6 +121,7 @@
private RecyclerView mRecyclerView;
private final ActionOnKeyListener mActionOnKeyListener;
private final ActionOnFocusListener mActionOnFocusListener;
+ private final ActionEditListener mActionEditListener;
private final List<GuidedAction> mActions;
private ClickListener mClickListener;
private GuidedActionsStylist mStylist;
@@ -126,13 +147,15 @@
* @param presenter The presenter that will manage the display of items in this adapter.
*/
public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
- FocusListener focusListener, GuidedActionsStylist presenter) {
+ FocusListener focusListener, EditListener editListener,
+ GuidedActionsStylist presenter) {
super();
mActions = new ArrayList<GuidedAction>(actions);
mClickListener = clickListener;
mStylist = presenter;
- mActionOnKeyListener = new ActionOnKeyListener(clickListener, mActions);
+ mActionOnKeyListener = new ActionOnKeyListener();
mActionOnFocusListener = new ActionOnFocusListener(focusListener);
+ mActionEditListener = new ActionEditListener(editListener);
}
/**
@@ -169,7 +192,6 @@
*/
public void setClickListener(ClickListener clickListener) {
mClickListener = clickListener;
- mActionOnKeyListener.setListener(clickListener);
}
/**
@@ -215,6 +237,16 @@
v.setOnClickListener(mOnClickListener);
v.setOnFocusChangeListener(mActionOnFocusListener);
+ final EditText edit = vh.getEditableTitleView();
+ if (edit != null) {
+ edit.setPrivateImeOptions("EscapeNorth=1;");
+ edit.setOnEditorActionListener(mActionEditListener);
+ if (edit instanceof ImeKeyMonitor) {
+ ImeKeyMonitor monitor = (ImeKeyMonitor)edit;
+ monitor.setImeKeyListener(mActionEditListener);
+ }
+ }
+
return new ActionViewHolder(v, vh);
}
@@ -230,6 +262,13 @@
GuidedAction action = mActions.get(position);
avh.setAction(action);
mStylist.onBindViewHolder(avh.mStylistViewHolder, action);
+
+ final EditText edit = avh.mStylistViewHolder.getEditableTitleView();
+ if (edit != null) {
+ int next = getNextEditableActionIndex(action);
+ int flag = (next == -1) ? EditorInfo.IME_ACTION_DONE : EditorInfo.IME_ACTION_NEXT;
+ edit.setImeOptions(flag);
+ }
}
/**
@@ -240,6 +279,25 @@
return mActions.size();
}
+ private int getNextEditableActionIndex(GuidedAction action) {
+ int i, size = mActions.size();
+ for (i = 0; i < size; i++) {
+ GuidedAction a = mActions.get(i);
+ if (mActions.get(i) == action) {
+ i++;
+ break;
+ }
+ }
+ for (; i < size; i++) {
+ GuidedAction a = mActions.get(i);
+ if (a.isEditable()) {
+ break;
+ }
+ }
+ int result = (i == size) ? -1 : i;
+ return result;
+ }
+
private class ActionOnFocusListener implements View.OnFocusChangeListener {
private FocusListener mFocusListener;
@@ -287,19 +345,7 @@
private class ActionOnKeyListener implements View.OnKeyListener {
- private final List<GuidedAction> mActions;
private boolean mKeyPressed = false;
- private ClickListener mClickListener;
-
- public ActionOnKeyListener(ClickListener listener,
- List<GuidedAction> actions) {
- mClickListener = listener;
- mActions = actions;
- }
-
- public void setListener(ClickListener listener) {
- mClickListener = listener;
- }
private void playSound(View v, int soundEffect) {
if (v.isSoundEffectsEnabled()) {
@@ -360,10 +406,14 @@
Log.d(TAG, "Enter Key up");
}
- mStylist.onAnimateItemPressed(avh.mStylistViewHolder,
- mKeyPressed);
- handleCheckedActions(avh, action);
- mClickListener.onGuidedActionClicked(action);
+ mStylist.onAnimateItemPressed(avh.mStylistViewHolder, mKeyPressed);
+ if (action.isEditable()) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme click");
+ mActionEditListener.openIme(avh, true);
+ } else {
+ handleCheckedActions(avh, action);
+ mClickListener.onGuidedActionClicked(action);
+ }
handled = true;
}
break;
@@ -403,4 +453,97 @@
}
}
}
+
+ private class ActionEditListener implements OnEditorActionListener,
+ ImeKeyMonitor.ImeKeyListener {
+
+ private EditListener mEditListener;
+
+ public ActionEditListener(EditListener listener) {
+ mEditListener = listener;
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME action: " + actionId);
+ boolean handled = false;
+ if (actionId == EditorInfo.IME_ACTION_NEXT ||
+ actionId == EditorInfo.IME_ACTION_DONE) {
+
+ ActionViewHolder avh = findSubChildViewHolder(v);
+ GuidedAction action = avh.getAction();
+ action.setTitle(v.getText());
+ mClickListener.onGuidedActionClicked(action);
+ int next = getNextEditableActionIndex(action);
+ if (next != -1) {
+ ViewHolder vh = mRecyclerView.findViewHolderForPosition(next);
+ if (vh != null) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme next/done");
+ handled = true;
+ openIme((ActionViewHolder)vh, false);
+ }
+ }
+ if (!handled) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme no next");
+ handled = true;
+ closeIme(avh);
+ }
+ } else if (actionId == EditorInfo.IME_ACTION_NONE) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
+ // Escape north handling: stay on current item, but close editor
+ handled = true;
+ ActionViewHolder avh = findSubChildViewHolder(v);
+ closeIme(avh);
+ }
+ return handled;
+ }
+
+ @Override
+ public boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME key: " + keyCode);
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
+ ActionViewHolder avh = findSubChildViewHolder(editText);
+ GuidedAction action = avh.getAction();
+ action.setTitle(editText.getText());
+ editText.clearFocus();
+ mEditListener.onGuidedActionEdited(action, false);
+ }
+ return false;
+ }
+
+ public void openIme(ActionViewHolder avh, boolean notify) {
+ View v = avh.mStylistViewHolder.getTitleView();
+ InputMethodManager mgr = (InputMethodManager)
+ v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ v.requestFocus();
+ mgr.showSoftInput(v, 0);
+ if (notify) {
+ mEditListener.onGuidedActionEdited(avh.getAction(), true);
+ }
+ }
+
+ public void closeIme(ActionViewHolder avh) {
+ View v = avh.mStylistViewHolder.getTitleView();
+ InputMethodManager mgr = (InputMethodManager)
+ v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ v.clearFocus();
+ mgr.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ mEditListener.onGuidedActionEdited(avh.getAction(), false);
+ }
+
+ private ActionViewHolder findSubChildViewHolder(View v) {
+ // Needed because RecyclerView.getChildViewHolder does not traverse the hierarchy
+ ActionViewHolder result = null;
+ ViewParent parent = v.getParent();
+ while (parent != mRecyclerView && parent != null && v != null) {
+ v = (View)parent;
+ parent = parent.getParent();
+ }
+ if (parent != null && v != null) {
+ result = (ActionViewHolder)mRecyclerView.getChildViewHolder(v);
+ }
+ return result;
+ }
+ }
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index b1e87a1..170aa63 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -211,6 +211,12 @@
}
/**
+ * Callback invoked when an action's title has been edited.
+ */
+ public void onGuidedActionEdited(GuidedAction action) {
+ }
+
+ /**
* Adds the specified GuidedStepFragment to the fragment stack, replacing any existing
* GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom animations.
* <p>
@@ -354,7 +360,17 @@
View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
actionContainer.addView(actionsView);
- mAdapter = new GuidedActionAdapter(mActions, this, this, mActionsStylist);
+ GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
+ @Override
+ public void onGuidedActionEdited(GuidedAction action, boolean entering) {
+ runImeAnimations(entering);
+ if (!entering) {
+ GuidedStepFragment.this.onGuidedActionEdited(action);
+ }
+ }
+ };
+
+ mAdapter = new GuidedActionAdapter(mActions, this, this, editListener, mActionsStylist);
mListView = mActionsStylist.getActionsGridView();
mListView.setAdapter(mAdapter);
@@ -522,6 +538,20 @@
});
}
+ private void runImeAnimations(boolean entering) {
+ ArrayList<Animator> animators = new ArrayList<Animator>();
+ if (entering) {
+ mGuidanceStylist.onImeAppearing(animators);
+ mActionsStylist.onImeAppearing(animators);
+ } else {
+ mGuidanceStylist.onImeDisappearing(animators);
+ mActionsStylist.onImeDisappearing(animators);
+ }
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animators);
+ set.start();
+ }
+
private Animator createDummyAnimator(final View v, ArrayList<Animator> animators) {
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animators);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
index fc93625..d828963 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -34,6 +34,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
import android.widget.FrameLayout;
import android.support.v17.leanback.R;
@@ -478,6 +479,17 @@
}
/**
+ * Displays the completions shown by the IME. An application may provide
+ * a list of query completions that the system will show in the IME.
+ *
+ * @param completions A list of completions to show in the IME. Setting to
+ * null or empty will clear the list.
+ */
+ public void displayCompletions(CompletionInfo[] completions) {
+ mSearchBar.displayCompletions(completions);
+ }
+
+ /**
* Sets this callback to have the fragment pass speech recognition requests
* to the activity rather than using an internal recognizer.
*/
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
index e06f7e8..7ff364e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -36,6 +36,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
import android.widget.FrameLayout;
import android.support.v17.leanback.R;
@@ -480,6 +481,17 @@
}
/**
+ * Displays the completions shown by the IME. An application may provide
+ * a list of query completions that the system will show in the IME.
+ *
+ * @param completions A list of completions to show in the IME. Setting to
+ * null or empty will clear the list.
+ */
+ public void displayCompletions(CompletionInfo[] completions) {
+ mSearchBar.displayCompletions(completions);
+ }
+
+ /**
* Sets this callback to have the fragment pass speech recognition requests
* to the activity rather than using an internal recognizer.
*/
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
index 6b6cc2e..c7d55b9 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -38,7 +38,7 @@
* <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
* an {@link ObjectAdapter}.
*/
-public class VerticalGridFragment extends BrandedFragment {
+public class VerticalGridFragment extends BaseFragment {
private static final String TAG = "VerticalGridFragment";
private static boolean DEBUG = false;
@@ -47,6 +47,7 @@
private VerticalGridPresenter.ViewHolder mGridViewHolder;
private OnItemViewSelectedListener mOnItemViewSelectedListener;
private OnItemViewClickedListener mOnItemViewClickedListener;
+ private Object mSceneAfterEntranceTransition;
private int mSelectedPosition = -1;
/**
@@ -170,6 +171,13 @@
gridDock.addView(mGridViewHolder.view);
mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
+ mSceneAfterEntranceTransition = sTransitionHelper.createScene(gridDock, new Runnable() {
+ @Override
+ public void run() {
+ setEntranceTransitionState(true);
+ }
+ });
+
updateAdapter();
}
@@ -183,7 +191,9 @@
public void onStart() {
super.onStart();
setupFocusSearchListener();
- mGridViewHolder.getGridView().requestFocus();
+ if (isEntranceTransitionEnabled()) {
+ setEntranceTransitionState(false);
+ }
}
@Override
@@ -210,4 +220,20 @@
}
}
}
+
+ @Override
+ protected Object createEntranceTransition() {
+ return sTransitionHelper.loadTransition(getActivity(),
+ R.transition.lb_vertical_grid_entrance_transition);
+ }
+
+ @Override
+ protected void runEntranceTransition(Object entranceTransition) {
+ sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+ entranceTransition);
+ }
+
+ void setEntranceTransitionState(boolean afterTransition) {
+ mGridPresenter.setEntranceTransitionState(mGridViewHolder, afterTransition);
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
index 0770761..33fe6fc 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -40,7 +40,7 @@
* <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
* an {@link ObjectAdapter}.
*/
-public class VerticalGridSupportFragment extends BrandedSupportFragment {
+public class VerticalGridSupportFragment extends BaseSupportFragment {
private static final String TAG = "VerticalGridSupportFragment";
private static boolean DEBUG = false;
@@ -49,6 +49,7 @@
private VerticalGridPresenter.ViewHolder mGridViewHolder;
private OnItemViewSelectedListener mOnItemViewSelectedListener;
private OnItemViewClickedListener mOnItemViewClickedListener;
+ private Object mSceneAfterEntranceTransition;
private int mSelectedPosition = -1;
/**
@@ -172,6 +173,13 @@
gridDock.addView(mGridViewHolder.view);
mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
+ mSceneAfterEntranceTransition = sTransitionHelper.createScene(gridDock, new Runnable() {
+ @Override
+ public void run() {
+ setEntranceTransitionState(true);
+ }
+ });
+
updateAdapter();
}
@@ -185,7 +193,9 @@
public void onStart() {
super.onStart();
setupFocusSearchListener();
- mGridViewHolder.getGridView().requestFocus();
+ if (isEntranceTransitionEnabled()) {
+ setEntranceTransitionState(false);
+ }
}
@Override
@@ -212,4 +222,20 @@
}
}
}
+
+ @Override
+ protected Object createEntranceTransition() {
+ return sTransitionHelper.loadTransition(getActivity(),
+ R.transition.lb_vertical_grid_entrance_transition);
+ }
+
+ @Override
+ protected void runEntranceTransition(Object entranceTransition) {
+ sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+ entranceTransition);
+ }
+
+ void setEntranceTransitionState(boolean afterTransition) {
+ mGridPresenter.setEntranceTransitionState(mGridViewHolder, afterTransition);
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
index 614f12b..085aac3 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.support.v17.leanback.R;
import android.util.AttributeSet;
import android.util.Log;
@@ -154,6 +155,14 @@
try {
mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY);
+ Drawable cardForeground = a.getDrawable(R.styleable.lbBaseCardView_cardForeground);
+ if (cardForeground != null) {
+ setForeground(cardForeground);
+ }
+ Drawable cardBackground = a.getDrawable(R.styleable.lbBaseCardView_cardBackground);
+ if (cardBackground != null) {
+ setBackground(cardBackground);
+ }
mInfoVisibility = a.getInteger(R.styleable.lbBaseCardView_infoVisibility,
CARD_REGION_VISIBLE_ACTIVATED);
mExtraVisibility = a.getInteger(R.styleable.lbBaseCardView_extraVisibility,
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
index 48829eb..87bbbd4 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -85,7 +85,7 @@
mWrapper = null;
}
mAnimator.setTimeListener(this);
- if (mWrapper != null && useDimmer) {
+ if (useDimmer) {
mDimmer = ColorOverlayDimmer.createDefault(view.getContext());
} else {
mDimmer = null;
@@ -99,9 +99,16 @@
mView.setScaleY(scale);
if (mWrapper != null) {
mWrapper.setShadowFocusLevel(level);
- if (mDimmer != null) {
- mDimmer.setActiveLevel(level);
- mWrapper.setOverlayColor(mDimmer.getPaint().getColor());
+ } else {
+ ShadowOverlayHelper.setNoneWrapperShadowFocusLevel(mView, level);
+ }
+ if (mDimmer != null) {
+ mDimmer.setActiveLevel(level);
+ int color = mDimmer.getPaint().getColor();
+ if (mWrapper != null) {
+ mWrapper.setOverlayColor(color);
+ } else {
+ ShadowOverlayHelper.setNoneWrapperOverlayColor(mView, color);
}
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
new file mode 100644
index 0000000..c9bed58
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
@@ -0,0 +1,78 @@
+package android.support.v17.leanback.widget;
+
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+
+final class ForegroundHelper {
+
+ final static ForegroundHelper sInstance = new ForegroundHelper();
+ ForegroundHelperVersionImpl mImpl;
+
+ /**
+ * Interface implemented by classes that support Shadow.
+ */
+ static interface ForegroundHelperVersionImpl {
+
+ public void setForeground(View view, Drawable drawable);
+
+ public Drawable getForeground(View view);
+ }
+
+ /**
+ * Implementation used on api 23 (and above).
+ */
+ private static final class ForegroundHelperApi23Impl implements ForegroundHelperVersionImpl {
+ @Override
+ public void setForeground(View view, Drawable drawable) {
+ ForegroundHelperApi23.setForeground(view, drawable);
+ }
+
+ @Override
+ public Drawable getForeground(View view) {
+ return ForegroundHelperApi23.getForeground(view);
+ }
+ }
+
+ /**
+ * Stub implementation
+ */
+ private static final class ForegroundHelperStubImpl implements ForegroundHelperVersionImpl {
+ @Override
+ public void setForeground(View view, Drawable drawable) {
+ }
+
+ @Override
+ public Drawable getForeground(View view) {
+ return null;
+ }
+ }
+
+ private ForegroundHelper() {
+ if (supportsForeground()) {
+ mImpl = new ForegroundHelperApi23Impl();
+ } else {
+ mImpl = new ForegroundHelperStubImpl();
+ }
+ }
+
+ public static ForegroundHelper getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * Returns true if view.setForeground() is supported.
+ */
+ public static boolean supportsForeground() {
+ return Build.VERSION.SDK_INT >= 23;
+ }
+
+ public Drawable getForeground(View view) {
+ return mImpl.getForeground(view);
+ }
+
+ public void setForeground(View view, Drawable drawable) {
+ mImpl.setForeground(view, drawable);
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java b/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
index 8bd0007..b1f6169 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
@@ -69,4 +69,16 @@
*/
public abstract void onFragmentReturn(@NonNull List<Animator> animators);
+ /**
+ * Animates the fragment in response to the IME appearing.
+ * @param animators A list of animations to which this provider's animations should be added.
+ */
+ public abstract void onImeAppearing(@NonNull List<Animator> animators);
+
+ /**
+ * Animates the fragment in response to the IME disappearing.
+ * @param animators A list of animations to which this provider's animations should be added.
+ */
+ public abstract void onImeDisappearing(@NonNull List<Animator> animators);
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
index 8d12510..b95c114 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
@@ -59,6 +59,8 @@
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepExitAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReentryAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReturnAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceContainerStyle
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceTitleStyle
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceDescriptionStyle
@@ -280,6 +282,28 @@
addAnimator(animators, mIconView, R.attr.guidedStepReturnAnimation);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onImeAppearing(@NonNull List<Animator> animators) {
+ addAnimator(animators, mTitleView, R.attr.guidedStepImeAppearingAnimation);
+ addAnimator(animators, mBreadcrumbView, R.attr.guidedStepImeAppearingAnimation);
+ addAnimator(animators, mDescriptionView, R.attr.guidedStepImeAppearingAnimation);
+ addAnimator(animators, mIconView, R.attr.guidedStepImeAppearingAnimation);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onImeDisappearing(@NonNull List<Animator> animators) {
+ addAnimator(animators, mTitleView, R.attr.guidedStepImeDisappearingAnimation);
+ addAnimator(animators, mBreadcrumbView, R.attr.guidedStepImeDisappearingAnimation);
+ addAnimator(animators, mDescriptionView, R.attr.guidedStepImeDisappearingAnimation);
+ addAnimator(animators, mIconView, R.attr.guidedStepImeDisappearingAnimation);
+ }
+
private void addAnimator(List<Animator> animators, View v, int attrId) {
if (v != null) {
Context ctx = v.getContext();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
index e4db2eb..30edc2f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -50,6 +50,7 @@
private boolean mMultilineDescription;
private boolean mHasNext;
private boolean mInfoOnly;
+ private boolean mEditable = false;
private int mCheckSetId = NO_CHECK_SET;
private boolean mEnabled = true;
private Intent mIntent;
@@ -68,6 +69,7 @@
// Subclass values
action.mIntent = mIntent;
+ action.mEditable = mEditable;
action.mChecked = mChecked;
action.mCheckSetId = mCheckSetId;
action.mMultilineDescription = mMultilineDescription;
@@ -138,11 +140,27 @@
}
/**
+ * Indicates whether this action is editable. Note: Editable actions cannot also be
+ * checked, or belong to a check set.
+ * @param editable Whether this action is editable.
+ */
+ public Builder editable(boolean editable) {
+ mEditable = editable;
+ if (mChecked || mCheckSetId != NO_CHECK_SET) {
+ throw new IllegalArgumentException("Editable actions cannot also be checked");
+ }
+ return this;
+ }
+
+ /**
* Indicates whether this action is initially checked.
* @param checked Whether this action is checked.
*/
public Builder checked(boolean checked) {
mChecked = checked;
+ if (mEditable) {
+ throw new IllegalArgumentException("Editable actions cannot also be checked");
+ }
return this;
}
@@ -154,6 +172,9 @@
*/
public Builder checkSetId(int checkSetId) {
mCheckSetId = checkSetId;
+ if (mEditable) {
+ throw new IllegalArgumentException("Editable actions cannot also be in check sets");
+ }
return this;
}
@@ -195,9 +216,10 @@
}
}
- private boolean mChecked;
+ private boolean mEditable;
private boolean mMultilineDescription;
private boolean mHasNext;
+ private boolean mChecked;
private boolean mInfoOnly;
private int mCheckSetId;
private boolean mEnabled;
@@ -217,6 +239,14 @@
}
/**
+ * Returns the title of this action.
+ * @return The title set when this action was built.
+ */
+ public void setTitle(CharSequence title) {
+ setLabel1(title);
+ }
+
+ /**
* Returns the description of this action.
* @return The description set when this action was built.
*/
@@ -233,6 +263,14 @@
}
/**
+ * Returns whether this action is editable.
+ * @return true if the action is editable, false otherwise.
+ */
+ public boolean isEditable() {
+ return mEditable;
+ }
+
+ /**
* Returns whether this action is checked.
* @return true if the action is currently checked, false otherwise.
*/
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
new file mode 100644
index 0000000..8e052fb
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener;
+import android.util.AttributeSet;
+import android.widget.EditText;
+import android.view.KeyEvent;
+
+/**
+ * A custom EditText that satisfies the IME key monitoring requirements of GuidedStepFragment.
+ */
+public class GuidedActionEditText extends EditText implements ImeKeyMonitor {
+
+ private ImeKeyListener mKeyListener;
+
+ public GuidedActionEditText(Context ctx) {
+ this(ctx, null);
+ }
+
+ public GuidedActionEditText(Context ctx, AttributeSet attrs) {
+ this(ctx, attrs, android.R.attr.editTextStyle);
+ }
+
+ public GuidedActionEditText(Context ctx, AttributeSet attrs, int defStyleAttr) {
+ super(ctx, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void setImeKeyListener(ImeKeyListener listener) {
+ mKeyListener = listener;
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ boolean result = false;
+ if (mKeyListener != null) {
+ result = mKeyListener.onKeyPreIme(this, keyCode, event);
+ }
+ if (!result) {
+ result = super.onKeyPreIme(keyCode, event);
+ }
+ return result;
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
index 15943b4..07dd4c9 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -40,6 +40,7 @@
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
@@ -81,7 +82,17 @@
* </ul><p>
* These view IDs are allowed to be missing, in which case the corresponding views in {@link
* GuidedActionsStylist.ViewHolder} will be null.
+ * <p>
+ * In order to support editable actions, the view associated with guidedactions_item_title should
+ * be a subclass of {@link android.widget.EditText}, and should satisfy the {@link
+ * ImeKeyMonitor} interface.
*
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepEntryAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepExitAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReentryAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReturnAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsEntryAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorShowAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorHideAnimation
@@ -158,6 +169,14 @@
}
/**
+ * Convenience method to return an editable version of the title, if possible,
+ * or null if the title view isn't an EditText.
+ */
+ public EditText getEditableTitleView() {
+ return (mTitleView instanceof EditText) ? (EditText)mTitleView : null;
+ }
+
+ /**
* Returns the description view within this view holder's view.
*/
public TextView getDescriptionView() {
@@ -300,7 +319,10 @@
* Subclasses may override to provide their own customized layouts. The base implementation
* returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden,
* the substituted layout should contain matching IDs for any views that should be managed by
- * the base class; this can be achieved by starting with a copy of the base layout file.
+ * the base class; this can be achieved by starting with a copy of the base layout file. Note
+ * that in order for the item to support editing, the title view should both subclass {@link
+ * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link
+ * GuidedActionEditText}.
* @return The resource ID of the layout to be inflated to define the view to display an
* individual GuidedAction.
*/
@@ -483,6 +505,24 @@
animators.add(createAnimator(mSelectorView, R.attr.guidedStepReturnAnimation));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onImeAppearing(@NonNull List<Animator> animators) {
+ animators.add(createAnimator(mActionsGridView, R.attr.guidedStepImeAppearingAnimation));
+ animators.add(createAnimator(mSelectorView, R.attr.guidedStepImeAppearingAnimation));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onImeDisappearing(@NonNull List<Animator> animators) {
+ animators.add(createAnimator(mActionsGridView, R.attr.guidedStepImeDisappearingAnimation));
+ animators.add(createAnimator(mSelectorView, R.attr.guidedStepImeDisappearingAnimation));
+ }
+
/*
* ==========================================
* Private methods
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
index 2c1c7e0..08eb617 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -18,60 +18,288 @@
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.v17.leanback.R;
-import android.text.TextUtils;
import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
+import android.widget.RelativeLayout;
import android.widget.TextView;
/**
- * A subclass of {@link BaseCardView} with an {@link ImageView} as its main region.
+ * A subclass of {@link BaseCardView} with an {@link ImageView} as its main
+ * region. The {@link ImageCardView} is highly customizable and can be used for
+ * various use-cases by adjusting the ImageViewCard's type to any combination of
+ * Title, Content, Badge or ImageOnly.
+ * <p>
+ * <h3>Styling</h3> There are three different ways to style the ImageCardView.
+ * <br>
+ * No matter what way you use, all your styles applied to an ImageCardView have
+ * to extend the style {@link R.style#Widget_Leanback_ImageCardViewStyle}.
+ * <p>
+ * <u>Example:</u><br>
+ *
+ * <pre>
+ * {@code <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
+ <item name="cardBackground">#F0F</item>
+ <item name="lbImageCardViewType">Title|Content</item>
+ <item name="lbImageCardViewInfoAreaStyle">@style/ImageCardViewColoredInfoArea</item>
+ <item name="lbImageCardViewTitleStyle">@style/ImageCardViewColoredTitle</item>
+ </style>}
+ * </pre>
+ * <p>
+ * The first possibility is to set a custom Style in the Leanback Theme's
+ * attribute <code>imageCardViewStyle</code>. The style set here, is the default
+ * style for all ImageCardViews. The other two possibilities allow you to style
+ * a particular ImageCardView. This is usefull if you want to create multiple
+ * types of cards. E.g. you might want to display a card with only a title and
+ * another one with title and content. Thus you need to define two different
+ * <code>ImageCardViewStyles</code> and apply them to the ImageCardViews. You
+ * can do this by either using a the {@link #ImageCardView(Context, int)}
+ * constructor and passing a style as second argument or by setting the style in
+ * a layout.
+ * <p>
+ * <u>Example (using constructor):</u><br>
+ *
+ * <pre>
+ * {@code
+ * new ImageCardView(context, R.style.CustomImageCardViewStyle);
+ * }
+ * </pre>
+ *
+ * <u>Example (using style attribute in a layout):</u><br>
+ *
+ * <pre>
+ * {@code <android.support.v17.leanback.widget.ImageCardView
+ android:id="@+id/imageCardView"
+ style="@style/CustomImageCardViewStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ </android.support.v17.leanback.widget.ImageCardView>}
+ * </pre>
+ * <p>
+ * You can style all ImageCardView's components such as the title, content,
+ * badge, infoArea and the image itself by extending the corresponding style and
+ * overriding the specific attribute in your custom
+ * <code>ImageCardViewStyle</code>.
+ *
+ * <h3>Components</h3> The ImageCardView contains three components which can be
+ * combined in any combination:
+ * <ul>
+ * <li>Title: The card's title</li>
+ * <li>Content: A short description</li>
+ * <li>Badge: An icon which can be displayed on the right or left side of the
+ * card.</li>
+ * </ul>
+ * In order to choose the components you want to use in your ImageCardView, you
+ * have to specify them in the <code>lbImageCardViewType</code> attribute of
+ * your custom <code>ImageCardViewStyle</code>. You can combine the following
+ * values: <code>Title, Content, IconOnRight, IconOnLeft, ImageOnly</code>.
+ * <p>
+ * <u>Examples:</u><br>
+ *
+ * <pre>
+ * {@code <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
+ ...
+ <item name="lbImageCardViewType">Title|Content|IconOnLeft</item>
+ ...
+ </style>}
+ * </pre>
+ *
+ * <pre>
+ * {@code <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
+ ...
+ <item name="lbImageCardViewType">ImageOnly</item>
+ ...
+ </style>}
+ * </pre>
+ *
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewStyle
+ * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewType
+ * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewTitleStyle
+ * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewContentStyle
+ * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewBadgeStyle
+ * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewImageStyle
+ * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewInfoAreaStyle
*/
public class ImageCardView extends BaseCardView {
+ public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0;
+ public static final int CARD_TYPE_FLAG_TITLE = 1;
+ public static final int CARD_TYPE_FLAG_CONTENT = 2;
+ public static final int CARD_TYPE_FLAG_ICON_RIGHT = 4;
+ public static final int CARD_TYPE_FLAG_ICON_LEFT = 8;
+
private ImageView mImageView;
- private View mInfoArea;
+ private ViewGroup mInfoArea;
private TextView mTitleView;
private TextView mContentView;
private ImageView mBadgeImage;
private boolean mAttachedToWindow;
+ /**
+ * Create an ImageCardView using a given style for customization.
+ *
+ * @param context
+ * The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param styleResId
+ * The resourceId of the style you want to apply to the
+ * ImageCardView. The style has to extend
+ * {@link R.style#Widget_Leanback_ImageCardViewStyle}.
+ */
+ public ImageCardView(Context context, int styleResId) {
+ super(new ContextThemeWrapper(context, styleResId), null, 0);
+ buildImageCardView(styleResId);
+ }
+
+ /**
+ * @see #View(Context, AttributeSet, int)
+ */
+ public ImageCardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(getStyledContext(context, attrs, defStyleAttr), attrs, defStyleAttr);
+ buildImageCardView(getImageCardViewStyle(context, attrs, defStyleAttr));
+ }
+
+ private void buildImageCardView(int styleResId) {
+ // Make sure the ImageCardView is focusable.
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ inflater.inflate(R.layout.lb_image_card_view, this);
+ TypedArray cardAttrs = getContext().obtainStyledAttributes(styleResId, R.styleable.lbImageCardView);
+ int cardType = cardAttrs.getInt(R.styleable.lbImageCardView_lbImageCardViewType, CARD_TYPE_FLAG_IMAGE_ONLY);
+ boolean hasImageOnly = cardType == CARD_TYPE_FLAG_IMAGE_ONLY;
+ boolean hasTitle = (cardType & CARD_TYPE_FLAG_TITLE) == CARD_TYPE_FLAG_TITLE;
+ boolean hasContent = (cardType & CARD_TYPE_FLAG_CONTENT) == CARD_TYPE_FLAG_CONTENT;
+ boolean hasIconRight = (cardType & CARD_TYPE_FLAG_ICON_RIGHT) == CARD_TYPE_FLAG_ICON_RIGHT;
+ boolean hasIconLeft = !hasIconRight && (cardType & CARD_TYPE_FLAG_ICON_LEFT) == CARD_TYPE_FLAG_ICON_LEFT;
+
+ mImageView = (ImageView) findViewById(R.id.main_image);
+ if (mImageView.getDrawable() == null) {
+ mImageView.setVisibility(View.INVISIBLE);
+ }
+
+ mInfoArea = (ViewGroup) findViewById(R.id.info_field);
+ if (hasImageOnly) {
+ removeView(mInfoArea);
+ cardAttrs.recycle();
+ return;
+ }
+ // Create children
+ if (hasTitle) {
+ mTitleView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_title, mInfoArea, false);
+ mInfoArea.addView(mTitleView);
+ }
+
+ if (hasContent) {
+ mContentView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_content, mInfoArea, false);
+ mInfoArea.addView(mContentView);
+ }
+
+ if (hasIconRight || hasIconLeft) {
+ int layoutId = R.layout.lb_image_card_view_themed_badge_right;
+ if (hasIconLeft) {
+ layoutId = R.layout.lb_image_card_view_themed_badge_left;
+ }
+ mBadgeImage = (ImageView) inflater.inflate(layoutId, mInfoArea, false);
+ mInfoArea.addView(mBadgeImage);
+ }
+
+ // Set up LayoutParams for children
+ if (hasTitle && !hasContent && mBadgeImage != null) {
+ RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mTitleView
+ .getLayoutParams();
+ // Adjust title TextView if there is an icon but no content
+ if (hasIconLeft) {
+ relativeLayoutParams.addRule(RelativeLayout.END_OF, mBadgeImage.getId());
+ } else {
+ relativeLayoutParams.addRule(RelativeLayout.START_OF, mBadgeImage.getId());
+ }
+ mTitleView.setLayoutParams(relativeLayoutParams);
+ }
+
+ // Set up LayoutParams for children
+ if (hasContent) {
+ RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mContentView
+ .getLayoutParams();
+ if (!hasTitle) {
+ relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ }
+ // Adjust content TextView if icon is on the left
+ if (hasIconLeft) {
+ relativeLayoutParams.removeRule(RelativeLayout.START_OF);
+ relativeLayoutParams.removeRule(RelativeLayout.ALIGN_PARENT_START);
+ relativeLayoutParams.addRule(RelativeLayout.END_OF, mBadgeImage.getId());
+ }
+ mContentView.setLayoutParams(relativeLayoutParams);
+ }
+
+ if (mBadgeImage != null) {
+ RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mBadgeImage
+ .getLayoutParams();
+ if (hasContent) {
+ relativeLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mContentView.getId());
+ } else if (hasTitle) {
+ relativeLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mTitleView.getId());
+ }
+ mBadgeImage.setLayoutParams(relativeLayoutParams);
+ }
+
+ // Backward compatibility: Newly created ImageCardViews should change
+ // the InfoArea's background color in XML using the corresponding style.
+ // However, since older implementations might make use of the
+ // 'infoAreaBackground' attribute, we have to make sure to support it.
+ // If the user has set a specific value here, it will differ from null.
+ // In this case, we do want to override the value set in the style.
+ Drawable background = cardAttrs.getDrawable(R.styleable.lbImageCardView_infoAreaBackground);
+ if (null != background) {
+ setInfoAreaBackground(background);
+ }
+ // Backward compatibility: There has to be an icon in the default
+ // version. If there is one, we have to set it's visibility to 'GONE'.
+ // Disabling 'adjustIconVisibility' allows the user to set the icon's
+ // visibility state in XML rather than code.
+ if (mBadgeImage != null && mBadgeImage.getDrawable() == null) {
+ mBadgeImage.setVisibility(View.GONE);
+ }
+ cardAttrs.recycle();
+ }
+
+ private static Context getStyledContext(Context context, AttributeSet attrs, int defStyleAttr) {
+ int style = getImageCardViewStyle(context, attrs, defStyleAttr);
+ return new ContextThemeWrapper(context, style);
+ }
+
+ private static int getImageCardViewStyle(Context context, AttributeSet attrs, int defStyleAttr) {
+ // Read style attribute defined in XML layout.
+ int style = null == attrs ? 0 : attrs.getStyleAttribute();
+ if (0 == style) {
+ // Not found? Read global ImageCardView style from Theme attribute.
+ TypedArray styledAttrs = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
+ style = styledAttrs.getResourceId(R.styleable.LeanbackTheme_imageCardViewStyle, 0);
+ styledAttrs.recycle();
+ }
+ return style;
+ }
+
+ /**
+ * @see #View(Context)
+ */
public ImageCardView(Context context) {
this(context, null);
}
+ /**
+ * @see #View(Context, AttributeSet)
+ */
public ImageCardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.imageCardViewStyle);
}
- public ImageCardView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- LayoutInflater inflater = LayoutInflater.from(context);
- View v = inflater.inflate(R.layout.lb_image_card_view, this);
-
- mImageView = (ImageView) v.findViewById(R.id.main_image);
- mImageView.setVisibility(View.INVISIBLE);
- mInfoArea = v.findViewById(R.id.info_field);
- mTitleView = (TextView) v.findViewById(R.id.title_text);
- mContentView = (TextView) v.findViewById(R.id.content_text);
- mBadgeImage = (ImageView) v.findViewById(R.id.extra_badge);
-
- if (mInfoArea != null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbImageCardView,
- defStyle, 0);
- try {
- setInfoAreaBackground(
- a.getDrawable(R.styleable.lbImageCardView_infoAreaBackground));
- } finally {
- a.recycle();
- }
- }
- }
-
/**
* Returns the main image view.
*/
@@ -170,7 +398,7 @@
/**
* Sets the info area background color.
- */
+ */
public void setInfoAreaBackgroundColor(@ColorInt int color) {
if (mInfoArea != null) {
mInfoArea.setBackgroundColor(color);
@@ -184,7 +412,6 @@
if (mTitleView == null) {
return;
}
-
mTitleView.setText(text);
}
@@ -206,7 +433,6 @@
if (mContentView == null) {
return;
}
-
mContentView.setText(text);
}
@@ -229,7 +455,7 @@
return;
}
mBadgeImage.setImageDrawable(drawable);
- if (drawable != null && mContentView!= null && mContentView.getVisibility() != GONE) {
+ if (drawable != null) {
mBadgeImage.setVisibility(View.VISIBLE);
} else {
mBadgeImage.setVisibility(View.GONE);
@@ -250,8 +476,8 @@
private void fadeIn() {
mImageView.setAlpha(0f);
if (mAttachedToWindow) {
- mImageView.animate().alpha(1f).setDuration(mImageView.getResources().getInteger(
- android.R.integer.config_shortAnimTime));
+ mImageView.animate().alpha(1f)
+ .setDuration(mImageView.getResources().getInteger(android.R.integer.config_shortAnimTime));
}
}
@@ -276,4 +502,5 @@
mImageView.setAlpha(1f);
super.onDetachedFromWindow();
}
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java b/v17/leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java
new file mode 100644
index 0000000..4691ad2
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+package android.support.v17.leanback.widget;
+
+import android.widget.EditText;
+import android.view.KeyEvent;
+
+/**
+ * Interface for an EditText subclass that can delegate calls to onKeyPreIme up to a registered
+ * listener.
+ * <p>
+ * Used in editable actions within {@link android.support.v17.leanback.app.GuidedStepFragment} to
+ * allow for custom back key handling. Specifically, this is used to implement the behavior that
+ * dismissing the IME also clears edit text focus. Clients who need to supply custom layouts for
+ * {@link GuidedActionsStylist} with their own EditText classes should satisfy this interface in
+ * order to inherit this behavior.
+ */
+public interface ImeKeyMonitor {
+
+ /**
+ * Listener interface for key events intercepted pre-IME by edit text objects.
+ */
+ public interface ImeKeyListener {
+ /**
+ * Callback invoked from EditText's onKeyPreIme method override. Returning true tells the
+ * caller that the key event is handled and should not be propagated.
+ */
+ public abstract boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event);
+ }
+
+ /**
+ * Set the listener for this edit text object. The listener's onKeyPreIme method will be
+ * invoked from the host edit text's onKeyPreIme method.
+ */
+ public void setImeKeyListener(ImeKeyListener listener);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java
new file mode 100644
index 0000000..19e6e9a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+
+/**
+ * A wrapper class working with {@link ItemBridgeAdapter} to wrap item view in a
+ * {@link ShadowOverlayContainer}. The ShadowOverlayContainer is created from conditions
+ * of {@link ShadowOverlayHelper}.
+ */
+public class ItemBridgeAdapterShadowOverlayWrapper extends ItemBridgeAdapter.Wrapper {
+
+ private final ShadowOverlayHelper mHelper;
+
+ public ItemBridgeAdapterShadowOverlayWrapper(ShadowOverlayHelper helper) {
+ mHelper = helper;
+ }
+
+ @Override
+ public View createWrapper(View root) {
+ Context context = root.getContext();
+ ShadowOverlayContainer wrapper = mHelper.createShadowOverlayContainer(context);
+ return wrapper;
+ }
+ @Override
+ public void wrap(View wrapper, View wrapped) {
+ ((ShadowOverlayContainer) wrapper).wrap(wrapped);
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index 9e588eb..29d2931 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -15,6 +15,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.support.v17.leanback.R;
import android.support.v17.leanback.system.Settings;
import android.util.Log;
@@ -96,6 +97,13 @@
}
@Override
+ protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ if (mShadowOverlayHelper != null) {
+ mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
+ }
+ }
+
+ @Override
public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
// Only when having an OnItemClickListner, we will attach the OnClickListener.
if (mRowViewHolder.getOnItemViewClickedListener() != null) {
@@ -122,9 +130,9 @@
@Override
public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
- if (needsDefaultListSelectEffect()) {
+ if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
int dimmedColor = mRowViewHolder.mColorDimmer.getPaint().getColor();
- ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
+ mShadowOverlayHelper.setOverlayColor(viewHolder.itemView, dimmedColor);
}
mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
}
@@ -144,7 +152,10 @@
private boolean mShadowEnabled = true;
private int mBrowseRowsFadingEdgeLength = -1;
private boolean mRoundedCornersEnabled = true;
+ private boolean mKeepChildForeground = true;
private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
+ private ShadowOverlayHelper mShadowOverlayHelper;
+ private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper;
private static int sSelectedRowTopPadding;
private static int sExpandedSelectedRowTopPadding;
@@ -253,44 +264,34 @@
return mUseFocusDimmer;
}
- private ItemBridgeAdapter.Wrapper mCardWrapper = new ItemBridgeAdapter.Wrapper() {
- @Override
- public View createWrapper(View root) {
- ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
- wrapper.setLayoutParams(
- new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
- if (isUsingZOrder(root.getContext())) {
- wrapper.useDynamicShadow();
- } else {
- wrapper.useStaticShadow();
- }
- wrapper.initialize(needsDefaultShadow(),
- needsDefaultListSelectEffect(),
- areChildRoundedCornersEnabled());
- return wrapper;
- }
- @Override
- public void wrap(View wrapper, View wrapped) {
- ((ShadowOverlayContainer) wrapper).wrap(wrapped);
- }
- };
-
@Override
protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
super.initializeRowViewHolder(holder);
final ViewHolder rowViewHolder = (ViewHolder) holder;
+ Context context = holder.view.getContext();
+ if (mShadowOverlayHelper == null) {
+ mShadowOverlayHelper = new ShadowOverlayHelper.Builder()
+ .needsOverlay(needsDefaultListSelectEffect())
+ .needsShadow(needsDefaultShadow())
+ .needsRoundedCorner(areChildRoundedCornersEnabled())
+ .preferZOrder(isUsingZOrder(context))
+ .keepForegroundDrawable(mKeepChildForeground)
+ .options(createShadowOverlayOptions())
+ .build(context);
+ if (mShadowOverlayHelper.needsWrapper()) {
+ mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper(
+ mShadowOverlayHelper);
+ }
+ }
rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder);
- if (needsDefaultListSelectEffect() || needsDefaultShadow()
- || areChildRoundedCornersEnabled()) {
- rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
- }
- if (needsDefaultShadow()) {
- ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
- }
+ // set wrapper if needed
+ rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper);
+ mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView);
+
FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
mFocusZoomFactor, mUseFocusDimmer);
- rowViewHolder.mGridView.setFocusDrawingOrderEnabled(
- !isUsingZOrder(rowViewHolder.getGridView().getContext()));
+ rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
+ == ShadowOverlayHelper.SHADOW_STATIC);
rowViewHolder.mGridView.setOnChildSelectedListener(
new OnChildSelectedListener() {
@Override
@@ -545,7 +546,7 @@
* Subclass may return false to disable.
*/
public boolean isUsingDefaultShadow() {
- return ShadowOverlayContainer.supportsShadow();
+ return ShadowOverlayHelper.supportsShadow();
}
/**
@@ -554,8 +555,7 @@
* and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
*/
public boolean isUsingZOrder(Context context) {
- return ShadowOverlayContainer.supportsDynamicShadow() &&
- !Settings.getInstance(context).preferStaticShadows();
+ return !Settings.getInstance(context).preferStaticShadows();
}
/**
@@ -595,9 +595,39 @@
return isUsingDefaultShadow() && getShadowEnabled();
}
- @Override
- public boolean canDrawOutOfBounds() {
- return needsDefaultShadow();
+ /**
+ * When ListRowPresenter applies overlay color on the child, it may change child's foreground
+ * Drawable. If application uses child's foreground for other purposes such as ripple effect,
+ * it needs tell ListRowPresenter to keep the child's foreground. The default value is true.
+ *
+ * @param keep true if keep foreground of child of this row, false ListRowPresenter might change
+ * the foreground of the child.
+ */
+ public final void setKeepChildForeground(boolean keep) {
+ mKeepChildForeground = keep;
+ }
+
+ /**
+ * Returns true if keeps foreground of child of this row, false ListRowPresenter might change
+ * child's foreground for applying overlay on the child.
+ *
+ * @return true if keeps foreground of child of this row, false otherwise.
+ */
+ public final boolean isKeepChildForeground() {
+ return mKeepChildForeground;
+ }
+
+ /**
+ * Create ShadowOverlayHelper Options. Subclass may override.
+ * e.g.
+ * <code>
+ * return new ShadowOverlayHelper.Options().roundedCornerRadius(10);
+ * </code>
+ *
+ * @return The options to be used for shadow, overlay and rouded corner.
+ */
+ protected ShadowOverlayHelper.Options createShadowOverlayOptions() {
+ return ShadowOverlayHelper.Options.DEFAULT;
}
/**
@@ -615,12 +645,11 @@
@Override
protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
super.onSelectLevelChanged(holder);
- if (needsDefaultListSelectEffect()) {
+ if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) {
ViewHolder vh = (ViewHolder) holder;
int dimmedColor = vh.mColorDimmer.getPaint().getColor();
for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
- ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i);
- wrapper.setOverlayColor(dimmedColor);
+ mShadowOverlayHelper.setOverlayColor(vh.mGridView.getChildAt(i), dimmedColor);
}
if (vh.mGridView.getFadingLeftEdge()) {
vh.mGridView.invalidate();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
index f1db00b..17225f8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
@@ -58,7 +58,7 @@
final TextView mCurrentTime;
final TextView mTotalTime;
final ProgressBar mProgressBar;
- int mCurrentTimeInSeconds;
+ int mCurrentTimeInSeconds = -1;
StringBuilder mTotalTimeStringBuilder = new StringBuilder();
StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
int mCurrentTimeMarginStart;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
index e1c5979..35a5b67 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
@@ -13,8 +13,7 @@
*/
package android.support.v17.leanback.widget;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.support.v17.leanback.R;
import android.os.Build;
import android.view.View;
@@ -24,7 +23,7 @@
final class RoundedRectHelper {
private final static RoundedRectHelper sInstance = new RoundedRectHelper();
- private Impl mImpl;
+ private final Impl mImpl;
/**
* Returns an instance of the helper.
@@ -33,15 +32,27 @@
return sInstance;
}
+ public static boolean supportsRoundedCorner() {
+ return Build.VERSION.SDK_INT >= 21;
+ }
+
+ /**
+ * Sets or removes a rounded rectangle outline on the given view.
+ */
+ public void setClipToRoundedOutline(View view, boolean clip, int radius) {
+ mImpl.setClipToRoundedOutline(view, clip, radius);
+ }
+
/**
* Sets or removes a rounded rectangle outline on the given view.
*/
public void setClipToRoundedOutline(View view, boolean clip) {
- mImpl.setClipToRoundedOutline(view, clip);
+ mImpl.setClipToRoundedOutline(view, clip, view.getResources().getDimensionPixelSize(
+ R.dimen.lb_rounded_rect_corner_radius));
}
static interface Impl {
- public void setClipToRoundedOutline(View view, boolean clip);
+ public void setClipToRoundedOutline(View view, boolean clip, int radius);
}
/**
@@ -49,7 +60,7 @@
*/
private static final class StubImpl implements Impl {
@Override
- public void setClipToRoundedOutline(View view, boolean clip) {
+ public void setClipToRoundedOutline(View view, boolean clip, int radius) {
// Not supported
}
}
@@ -59,13 +70,13 @@
*/
private static final class Api21Impl implements Impl {
@Override
- public void setClipToRoundedOutline(View view, boolean clip) {
- RoundedRectHelperApi21.setClipToRoundedOutline(view, clip);
+ public void setClipToRoundedOutline(View view, boolean clip, int radius) {
+ RoundedRectHelperApi21.setClipToRoundedOutline(view, clip, radius);
}
}
private RoundedRectHelper() {
- if (Build.VERSION.SDK_INT >= 21) {
+ if (supportsRoundedCorner()) {
mImpl = new Api21Impl();
} else {
mImpl = new StubImpl();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index 4f078f1..cb1f2ac7 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -565,13 +565,6 @@
return mHeaderPresenter != null || needsDefaultSelectEffect();
}
- /**
- * Returns true if the Row view can draw outside its bounds.
- */
- public boolean canDrawOutOfBounds() {
- return false;
- }
-
@Override
public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
onBindRowViewHolder(getRowViewHolder(viewHolder), item);
@@ -642,12 +635,16 @@
* Changes the visibility of views. The entrance transition will be run against the views that
* change visibilities. A subclass may override and begin with calling
* super.setEntranceTransitionState(). This method is called by the fragment,
- * it should not call it directly by the application.
+ * it should not be called directly by the application.
+ *
+ * @param holder The ViewHolder of the row.
+ * @param afterEntrance true if children of row participating in entrance transition
+ * should be set to visible, false otherwise.
*/
- public void setEntranceTransitionState(ViewHolder holder, boolean afterTransition) {
+ public void setEntranceTransitionState(ViewHolder holder, boolean afterEntrance) {
if (holder.mHeaderViewHolder != null &&
holder.mHeaderViewHolder.view.getVisibility() != View.GONE) {
- holder.mHeaderViewHolder.view.setVisibility(afterTransition ?
+ holder.mHeaderViewHolder.view.setVisibility(afterEntrance ?
View.VISIBLE : View.INVISIBLE);
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
index acdebcb..1c3835f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -406,15 +406,23 @@
* @param completions list of completions shown in the IME, can be null or empty to clear them
*/
public void displayCompletions(List<String> completions) {
- List<CompletionInfo> infos = new ArrayList<CompletionInfo>();
+ List<CompletionInfo> infos = new ArrayList<>();
if (null != completions) {
for (String completion : completions) {
infos.add(new CompletionInfo(infos.size(), infos.size(), completion));
}
}
+ CompletionInfo[] array = new CompletionInfo[infos.size()];
+ displayCompletions(infos.toArray(array));
+ }
- mInputMethodManager.displayCompletions(mSearchTextEditor,
- infos.toArray(new CompletionInfo[] {}));
+ /**
+ * Updates the completion list shown by the IME
+ *
+ * @param completions list of completions shown in the IME, can be null or empty to clear them
+ */
+ public void displayCompletions(CompletionInfo[] completions) {
+ mInputMethodManager.displayCompletions(mSearchTextEditor, completions);
}
/**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
index be70575..30348b8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
@@ -32,7 +32,7 @@
*/
static interface ShadowHelperVersionImpl {
public Object addDynamicShadow(
- ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners);
+ View shadowContainer, float unfocusedZ, float focusedZ, int roundedCornerRadius);
public void setZ(View view, float z);
public void setShadowFocusLevel(Object impl, float level);
}
@@ -43,7 +43,7 @@
private static final class ShadowHelperStubImpl implements ShadowHelperVersionImpl {
@Override
public Object addDynamicShadow(
- ViewGroup shadowContainer, float focusedZ, float unfocusedZ, boolean roundedCorners) {
+ View shadowContainer, float focusedZ, float unfocusedZ, int roundedCornerRadius) {
// do nothing
return null;
}
@@ -65,9 +65,9 @@
private static final class ShadowHelperApi21Impl implements ShadowHelperVersionImpl {
@Override
public Object addDynamicShadow(
- ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
+ View shadowContainer, float unfocusedZ, float focusedZ, int roundedCornerRadius) {
return ShadowHelperApi21.addDynamicShadow(
- shadowContainer, unfocusedZ, focusedZ, roundedCorners);
+ shadowContainer, unfocusedZ, focusedZ, roundedCornerRadius);
}
@Override
@@ -102,8 +102,8 @@
}
public Object addDynamicShadow(
- ViewGroup shadowContainer, float unfocusedZ, float focusedZ, boolean roundedCorners) {
- return mImpl.addDynamicShadow(shadowContainer, unfocusedZ, focusedZ, roundedCorners);
+ View shadowContainer, float unfocusedZ, float focusedZ, int roundedCornerRadius) {
+ return mImpl.addDynamicShadow(shadowContainer, unfocusedZ, focusedZ, roundedCornerRadius);
}
public void setShadowFocusLevel(Object impl, float level) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
index e367494..3be5929 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
@@ -20,11 +20,16 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
import android.graphics.Rect;
/**
* Provides an SDK version-independent wrapper to support shadows, color overlays, and rounded
- * corners.
+ * corners. It's not always preferred to create a ShadowOverlayContainer, use
+ * {@link ShadowOverlayHelper} instead.
* <p>
* {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
* before using shadow. Depending on sdk version, optical bounds might be applied
@@ -46,41 +51,52 @@
* Call {@link #setOverlayColor(int)} to control overlay color.
* </p>
*/
-public class ShadowOverlayContainer extends ViewGroup {
+public class ShadowOverlayContainer extends FrameLayout {
/**
* No shadow.
*/
- public static final int SHADOW_NONE = 1;
+ public static final int SHADOW_NONE = ShadowOverlayHelper.SHADOW_NONE;
/**
* Shadows are fixed.
*/
- public static final int SHADOW_STATIC = 2;
+ public static final int SHADOW_STATIC = ShadowOverlayHelper.SHADOW_STATIC;
/**
* Shadows depend on the size, shape, and position of the view.
*/
- public static final int SHADOW_DYNAMIC = 3;
+ public static final int SHADOW_DYNAMIC = ShadowOverlayHelper.SHADOW_DYNAMIC;
private boolean mInitialized;
- private View mColorDimOverlay;
private Object mShadowImpl;
private View mWrappedView;
private boolean mRoundedCorners;
private int mShadowType = SHADOW_NONE;
private float mUnfocusedZ;
private float mFocusedZ;
+ private int mRoundedCornerRadius;
private static final Rect sTempRect = new Rect();
+ private Paint mOverlayPaint;
+ private int mOverlayColor;
+ /**
+ * Create ShadowOverlayContainer and auto select shadow type.
+ */
public ShadowOverlayContainer(Context context) {
this(context, null, 0);
}
+ /**
+ * Create ShadowOverlayContainer and auto select shadow type.
+ */
public ShadowOverlayContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
+ /**
+ * Create ShadowOverlayContainer and auto select shadow type.
+ */
public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
useStaticShadow();
@@ -88,6 +104,18 @@
}
/**
+ * Create ShadowOverlayContainer with specific shadowType.
+ */
+ ShadowOverlayContainer(Context context,
+ int shadowType, boolean hasColorDimOverlay,
+ float unfocusedZ, float focusedZ, int roundedCornerRadius) {
+ super(context);
+ mUnfocusedZ = unfocusedZ;
+ mFocusedZ = focusedZ;
+ initialize(shadowType, hasColorDimOverlay, roundedCornerRadius);
+ }
+
+ /**
* Return true if the platform sdk supports shadow.
*/
public static boolean supportsShadow() {
@@ -155,7 +183,7 @@
/**
* Initialize shadows, color overlay.
- * @deprecated use {@link #initialize(boolean, boolean, boolean)} instead.
+ * @deprecated use {@link ShadowOverlayHelper#createShadowOverlayContainer(Context)} instead.
*/
@Deprecated
public void initialize(boolean hasShadow, boolean hasColorDimOverlay) {
@@ -164,29 +192,62 @@
/**
* Initialize shadows, color overlay, and rounded corners. All are optional.
+ * Shadow type are auto-selected based on {@link #useStaticShadow()} and
+ * {@link #useDynamicShadow()} call.
+ * @deprecated use {@link ShadowOverlayHelper#createShadowOverlayContainer(Context)} instead.
*/
+ @Deprecated
public void initialize(boolean hasShadow, boolean hasColorDimOverlay, boolean roundedCorners) {
+ int shadowType;
+ if (!hasShadow) {
+ shadowType = SHADOW_NONE;
+ } else {
+ shadowType = mShadowType;
+ }
+ int roundedCornerRadius = roundedCorners ? getContext().getResources().getDimensionPixelSize(
+ R.dimen.lb_rounded_rect_corner_radius) : 0;
+ initialize(shadowType, hasColorDimOverlay, roundedCornerRadius);
+ }
+
+ /**
+ * Initialize shadows, color overlay, and rounded corners. All are optional.
+ */
+ void initialize(int shadowType, boolean hasColorDimOverlay, int roundedCornerRadius) {
if (mInitialized) {
throw new IllegalStateException();
}
mInitialized = true;
- if (hasShadow) {
- switch (mShadowType) {
- case SHADOW_DYNAMIC:
- mShadowImpl = ShadowHelper.getInstance().addDynamicShadow(
- this, mUnfocusedZ, mFocusedZ, roundedCorners);
- break;
- case SHADOW_STATIC:
- mShadowImpl = StaticShadowHelper.getInstance().addStaticShadow(
- this, roundedCorners);
- break;
- }
+ mRoundedCornerRadius = roundedCornerRadius;
+ mRoundedCorners = roundedCornerRadius > 0;
+ mShadowType = shadowType;
+ switch (mShadowType) {
+ case SHADOW_DYNAMIC:
+ mShadowImpl = ShadowHelper.getInstance().addDynamicShadow(
+ this, mUnfocusedZ, mFocusedZ, mRoundedCornerRadius);
+ break;
+ case SHADOW_STATIC:
+ mShadowImpl = StaticShadowHelper.getInstance().addStaticShadow(this);
+ break;
}
- mRoundedCorners = roundedCorners;
if (hasColorDimOverlay) {
- mColorDimOverlay = LayoutInflater.from(getContext())
- .inflate(R.layout.lb_card_color_overlay, this, false);
- addView(mColorDimOverlay);
+ setWillNotDraw(false);
+ mOverlayColor = Color.TRANSPARENT;
+ mOverlayPaint = new Paint();
+ mOverlayPaint.setColor(mOverlayColor);
+ mOverlayPaint.setStyle(Paint.Style.FILL);
+ } else {
+ setWillNotDraw(true);
+ mOverlayPaint = null;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mOverlayPaint != null && mOverlayColor != Color.TRANSPARENT) {
+ canvas.drawRect(mWrappedView.getLeft(), mWrappedView.getTop(),
+ mWrappedView.getRight(), mWrappedView.getBottom(),
+ mOverlayPaint);
}
}
@@ -195,19 +256,7 @@
*/
public void setShadowFocusLevel(float level) {
if (mShadowImpl != null) {
- if (level < 0f) {
- level = 0f;
- } else if (level > 1f) {
- level = 1f;
- }
- switch (mShadowType) {
- case SHADOW_DYNAMIC:
- ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
- break;
- case SHADOW_STATIC:
- StaticShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
- break;
- }
+ ShadowOverlayHelper.setShadowFocusLevel(mShadowImpl, mShadowType, level);
}
}
@@ -215,8 +264,12 @@
* Set color (with alpha) of the overlay.
*/
public void setOverlayColor(@ColorInt int overlayColor) {
- if (mColorDimOverlay != null) {
- mColorDimOverlay.setBackgroundColor(overlayColor);
+ if (mOverlayPaint != null) {
+ if (overlayColor != mOverlayColor) {
+ mOverlayColor = overlayColor;
+ mOverlayPaint.setColor(overlayColor);
+ invalidate();
+ }
}
}
@@ -227,15 +280,24 @@
if (!mInitialized || mWrappedView != null) {
throw new IllegalStateException();
}
- if (mColorDimOverlay != null) {
- addView(view, indexOfChild(mColorDimOverlay));
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp != null) {
+ // if wrapped view has layout params, inherit everything but width/height.
+ // Wrapped view is assigned a FrameLayout.LayoutParams with width
+ // and height only. User can still change wrapped view width/height afterwards.
+ // Margins, etc are assigned to the wrapper and take effect in parent container.
+ ViewGroup.LayoutParams wrapped_lp = new FrameLayout.LayoutParams(lp.width, lp.height);
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ this.setLayoutParams(lp);
+ addView(view, wrapped_lp);
} else {
addView(view);
}
- mWrappedView = view;
- if (mRoundedCorners) {
- RoundedRectHelper.getInstance().setClipToRoundedOutline(mWrappedView, true);
+ if (mRoundedCorners && mShadowType == SHADOW_STATIC) {
+ RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
}
+ mWrappedView = view;
}
/**
@@ -246,67 +308,9 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mWrappedView == null) {
- throw new IllegalStateException();
- }
- // padding and child margin are not supported.
- // first measure the wrapped view, then measure the shadow view and/or overlay view.
- int childWidthMeasureSpec, childHeightMeasureSpec;
- LayoutParams lp = mWrappedView.getLayoutParams();
- if (lp.width == LayoutParams.MATCH_PARENT) {
- childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
- (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
- } else {
- childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
- }
- if (lp.height == LayoutParams.MATCH_PARENT) {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
- (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
- } else {
- childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
- }
- mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-
- int measuredWidth = mWrappedView.getMeasuredWidth();
- int measuredHeight = mWrappedView.getMeasuredHeight();
-
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child == mWrappedView) {
- continue;
- }
- lp = child.getLayoutParams();
- if (lp.width == LayoutParams.MATCH_PARENT) {
- childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
- (measuredWidth, MeasureSpec.EXACTLY);
- } else {
- childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
- }
-
- if (lp.height == LayoutParams.MATCH_PARENT) {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
- (measuredHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
- }
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- setMeasuredDimension(measuredWidth, measuredHeight);
- }
-
- @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final int width = child.getMeasuredWidth();
- final int height = child.getMeasuredHeight();
- child.layout(0, 0, width, height);
- }
- }
- if (mWrappedView != null) {
+ super.onLayout(changed, l, t, r, b);
+ if (changed && mWrappedView != null) {
sTempRect.left = (int) mWrappedView.getPivotX();
sTempRect.top = (int) mWrappedView.getPivotY();
offsetDescendantRectToMyCoords(mWrappedView, sTempRect);
@@ -315,4 +319,8 @@
}
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
new file mode 100644
index 0000000..4843b58
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
@@ -0,0 +1,467 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.system.Settings;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.View;
+
+
+/**
+ * ShadowOverlayHelper is a helper class for shadow, overlay color and rounded corner.
+ * There are many choices to implement Shadow, overlay color.
+ * Initialize it with ShadowOverlayHelper.Builder and it decides the best strategy based
+ * on options user choose and current platform version.
+ *
+ * <li> For shadow: it may use 9-patch with opticalBounds or Z-value based shadow for
+ * API >= 21. When 9-patch is used, it requires a ShadowOverlayContainer
+ * to include 9-patch views.
+ * <li> For overlay: it may use ShadowOverlayContainer which overrides draw() or it may
+ * use setForeground(new ColorDrawable()) for API>=23. The foreground support
+ * might be disabled if rounded corner is applied due to performance reason.
+ * <li> For rounded-corner: it uses a ViewOutlineProvider for API>=21.
+ *
+ * There are two different strategies: use Wrapper with a ShadowOverlayContainer;
+ * or apply rounded corner, overlay and rounded-corner to the view itself. Below is an example
+ * of how helper is used.
+ *
+ * <code>
+ * ShadowOverlayHelper mHelper = new ShadowOverlayHelper.Builder().
+ * .needsOverlay(true).needsRoundedCorner(true).needsShadow(true)
+ * .build();
+ * mHelper.prepareParentForShadow(parentView); // apply optical-bounds for 9-patch shadow.
+ * mHelper.setOverlayColor(view, Color.argb(0x80, 0x80, 0x80, 0x80));
+ * mHelper.setShadowFocusLevel(view, 1.0f);
+ * ...
+ * View initializeView(View view) {
+ * if (mHelper.needsWrapper()) {
+ * ShadowOverlayContainer wrapper = mHelper.createShadowOverlayContainer(context);
+ * wrapper.wrap(view);
+ * return wrapper;
+ * } else {
+ * mHelper.onViewCreated(view);
+ * return view;
+ * }
+ * }
+ * ...
+ *
+ * </code>
+ */
+public final class ShadowOverlayHelper {
+
+ /**
+ * Builder for creating ShadowOverlayHelper.
+ */
+ public static final class Builder {
+
+ private boolean needsOverlay;
+ private boolean needsRoundedCorner;
+ private boolean needsShadow;
+ private boolean preferZOrder = true;
+ private boolean keepForegroundDrawable;
+ private Options options = Options.DEFAULT;
+
+ /**
+ * Set if needs overlay color.
+ * @param needsOverlay True if needs overlay.
+ * @return The Builder object itself.
+ */
+ public Builder needsOverlay(boolean needsOverlay) {
+ this.needsOverlay = needsOverlay;
+ return this;
+ }
+
+ /**
+ * Set if needs shadow.
+ * @param needsShadow True if needs shadow.
+ * @return The Builder object itself.
+ */
+ public Builder needsShadow(boolean needsShadow) {
+ this.needsShadow = needsShadow;
+ return this;
+ }
+
+ /**
+ * Set if needs rounded corner.
+ * @param needsRoundedCorner True if needs rounded corner.
+ * @return The Builder object itself.
+ */
+ public Builder needsRoundedCorner(boolean needsRoundedCorner) {
+ this.needsRoundedCorner = needsRoundedCorner;
+ return this;
+ }
+
+ /**
+ * Set if prefer z-order shadow. On old devices, z-order shadow might be slow,
+ * set to false to fall back to static 9-patch shadow. Recommend to read
+ * from system wide Setting value: see {@link Settings}.
+ *
+ * @param preferZOrder True if prefer Z shadow. Default is true.
+ * @return The Builder object itself.
+ */
+ public Builder preferZOrder(boolean preferZOrder) {
+ this.preferZOrder = preferZOrder;
+ return this;
+ }
+
+ /**
+ * Set if not using foreground drawable for overlay color. For example if
+ * the view has already assigned a foreground drawable for other purposes.
+ * When it's true, helper will use a ShadowOverlayContainer for overlay color.
+ *
+ * @param keepForegroundDrawable True to keep the original foreground drawable.
+ * @return The Builder object itself.
+ */
+ public Builder keepForegroundDrawable(boolean keepForegroundDrawable) {
+ this.keepForegroundDrawable = keepForegroundDrawable;
+ return this;
+ }
+
+ /**
+ * Set option values e.g. Shadow Z value, rounded corner radius.
+ *
+ * @param options The Options object to create ShadowOverlayHelper.
+ */
+ public Builder options(Options options) {
+ this.options = options;
+ return this;
+ }
+
+ /**
+ * Create ShadowOverlayHelper object
+ * @param context The context uses to read Resources settings.
+ * @return The ShadowOverlayHelper object.
+ */
+ public ShadowOverlayHelper build(Context context) {
+ final ShadowOverlayHelper helper = new ShadowOverlayHelper();
+ helper.mNeedsOverlay = needsOverlay;
+ helper.mNeedsRoundedCorner = needsRoundedCorner && supportsRoundedCorner();
+ helper.mNeedsShadow = needsShadow && supportsShadow();
+
+ if (helper.mNeedsRoundedCorner) {
+ helper.setupRoundedCornerRadius(options, context);
+ }
+
+ // figure out shadow type and if we need use wrapper:
+ if (helper.mNeedsShadow) {
+ // if static shadow is prefered or dynamic shadow is not supported,
+ // use static shadow, otherwise use dynamic shadow.
+ if (!preferZOrder || !supportsDynamicShadow()) {
+ helper.mShadowType = SHADOW_STATIC;
+ // static shadow requires ShadowOverlayContainer to support crossfading
+ // of two shadow views.
+ helper.mNeedsWrapper = true;
+ } else {
+ helper.mShadowType = SHADOW_DYNAMIC;
+ helper.setupDynamicShadowZ(options, context);
+ helper.mNeedsWrapper = ((!supportsForeground() || keepForegroundDrawable)
+ && helper.mNeedsOverlay);
+ }
+ } else {
+ helper.mShadowType = SHADOW_NONE;
+ helper.mNeedsWrapper = ((!supportsForeground() || keepForegroundDrawable)
+ && helper.mNeedsOverlay);
+ }
+
+ return helper;
+ }
+
+ }
+
+ /**
+ * Option values for ShadowOverlayContainer.
+ */
+ public static final class Options {
+
+ /**
+ * Default Options for values.
+ */
+ public static final Options DEFAULT = new Options();
+
+ private int roundedCornerRadius = 0; // 0 for default value
+ private float dynamicShadowUnfocusedZ = -1; // < 0 for default value
+ private float dynamicShadowFocusedZ = -1; // < 0 for default value
+ /**
+ * Set value of rounded corner radius.
+ *
+ * @param roundedCornerRadius Number of pixels of rounded corner radius.
+ * Set to 0 to use default settings.
+ * @return The Options object itself.
+ */
+ public Options roundedCornerRadius(int roundedCornerRadius){
+ this.roundedCornerRadius = roundedCornerRadius;
+ return this;
+ }
+
+ /**
+ * Set value of focused and unfocused Z value for shadow.
+ *
+ * @param unfocusedZ Number of pixels for unfocused Z value.
+ * @param focusedZ Number of pixels for foucsed Z value.
+ * @return The Options object itself.
+ */
+ public Options dynamicShadowZ(float unfocusedZ, float focusedZ){
+ this.dynamicShadowUnfocusedZ = unfocusedZ;
+ this.dynamicShadowFocusedZ = focusedZ;
+ return this;
+ }
+
+ /**
+ * Get radius of rounded corner in pixels.
+ *
+ * @return Radius of rounded corner in pixels.
+ */
+ public final int getRoundedCornerRadius() {
+ return roundedCornerRadius;
+ }
+
+ /**
+ * Get z value of shadow when a view is not focused.
+ *
+ * @return Z value of shadow when a view is not focused.
+ */
+ public final float getDynamicShadowUnfocusedZ() {
+ return dynamicShadowUnfocusedZ;
+ }
+
+ /**
+ * Get z value of shadow when a view is focused.
+ *
+ * @return Z value of shadow when a view is focused.
+ */
+ public final float getDynamicShadowFocusedZ() {
+ return dynamicShadowFocusedZ;
+ }
+ }
+
+ /**
+ * No shadow.
+ */
+ public static final int SHADOW_NONE = 1;
+
+ /**
+ * Shadows are fixed.
+ */
+ public static final int SHADOW_STATIC = 2;
+
+ /**
+ * Shadows depend on the size, shape, and position of the view.
+ */
+ public static final int SHADOW_DYNAMIC = 3;
+
+ int mShadowType = SHADOW_NONE;
+ boolean mNeedsOverlay;
+ boolean mNeedsRoundedCorner;
+ boolean mNeedsShadow;
+ boolean mNeedsWrapper;
+
+ int mRoundedCornerRadius;
+ float mUnfocusedZ;
+ float mFocusedZ;
+
+ /**
+ * Return true if the platform sdk supports shadow.
+ */
+ public static boolean supportsShadow() {
+ return StaticShadowHelper.getInstance().supportsShadow();
+ }
+
+ /**
+ * Returns true if the platform sdk supports dynamic shadows.
+ */
+ public static boolean supportsDynamicShadow() {
+ return ShadowHelper.getInstance().supportsDynamicShadow();
+ }
+
+ /**
+ * Returns true if the platform sdk supports rounded corner through outline.
+ */
+ public static boolean supportsRoundedCorner() {
+ return RoundedRectHelper.supportsRoundedCorner();
+ }
+
+ /**
+ * Returns true if view.setForeground() is supported.
+ */
+ public static boolean supportsForeground() {
+ return ForegroundHelper.supportsForeground();
+ }
+
+ /*
+ * hide from external, should be only created by ShadowOverlayHelper.Options.
+ */
+ ShadowOverlayHelper() {
+ }
+
+ /**
+ * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
+ * before using shadow. Depending on Shadow type, optical bounds might be applied.
+ */
+ public void prepareParentForShadow(ViewGroup parent) {
+ if (mShadowType == SHADOW_STATIC) {
+ StaticShadowHelper.getInstance().prepareParent(parent);
+ }
+ }
+
+ public int getShadowType() {
+ return mShadowType;
+ }
+
+ public boolean needsOverlay() {
+ return mNeedsOverlay;
+ }
+
+ public boolean needsRoundedCorner() {
+ return mNeedsRoundedCorner;
+ }
+
+ /**
+ * Returns true if a "wrapper" ShadowOverlayContainer is needed.
+ * When needsWrapper() is true, call {@link #createShadowOverlayContainer(Context)}
+ * to create the wrapper.
+ */
+ public boolean needsWrapper() {
+ return mNeedsWrapper;
+ }
+
+ /**
+ * Create ShadowOverlayContainer for this helper.
+ * @param context Context to create view.
+ * @return ShadowOverlayContainer.
+ */
+ public ShadowOverlayContainer createShadowOverlayContainer(Context context) {
+ if (!needsWrapper()) {
+ throw new IllegalArgumentException();
+ }
+ return new ShadowOverlayContainer(context, mShadowType, mNeedsOverlay,
+ mUnfocusedZ, mFocusedZ, mRoundedCornerRadius);
+ }
+
+ /**
+ * Set overlay color for view other than ShadowOverlayContainer.
+ * See also {@link ShadowOverlayContainer#setOverlayColor(int)}.
+ */
+ public static void setNoneWrapperOverlayColor(View view, int color) {
+ Drawable d = ForegroundHelper.getInstance().getForeground(view);
+ if (d instanceof ColorDrawable) {
+ ((ColorDrawable) d).setColor(color);
+ } else {
+ ForegroundHelper.getInstance().setForeground(view, new ColorDrawable(color));
+ }
+ }
+
+ /**
+ * Set overlay color for view, it can be a ShadowOverlayContainer if needsWrapper() is true,
+ * or other view type.
+ */
+ public void setOverlayColor(View view, int color) {
+ if (needsWrapper()) {
+ ((ShadowOverlayContainer) view).setOverlayColor(color);
+ } else {
+ setNoneWrapperOverlayColor(view, color);
+ }
+ }
+
+ /**
+ * Must be called when view is created for cases {@link #needsWrapper()} is false.
+ * @param view
+ */
+ public void onViewCreated(View view) {
+ if (!needsWrapper()) {
+ if (!mNeedsShadow) {
+ if (mNeedsRoundedCorner) {
+ RoundedRectHelper.getInstance().setClipToRoundedOutline(view,
+ true, mRoundedCornerRadius);
+ }
+ } else {
+ if (mShadowType == SHADOW_DYNAMIC) {
+ Object tag = ShadowHelper.getInstance().addDynamicShadow(
+ view, mUnfocusedZ, mFocusedZ, mRoundedCornerRadius);
+ view.setTag(R.id.lb_shadow_impl, tag);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
+ * This is for view other than ShadowOverlayContainer.
+ * See also {@link ShadowOverlayContainer#setShadowFocusLevel(float)}.
+ */
+ public static void setNoneWrapperShadowFocusLevel(View view, float level) {
+ setShadowFocusLevel(getNoneWrapperDyamicShadowImpl(view), SHADOW_DYNAMIC, level);
+ }
+
+ /**
+ * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
+ */
+ public void setShadowFocusLevel(View view, float level) {
+ if (needsWrapper()) {
+ ((ShadowOverlayContainer) view).setShadowFocusLevel(level);
+ } else {
+ setShadowFocusLevel(getNoneWrapperDyamicShadowImpl(view), SHADOW_DYNAMIC, level);
+ }
+ }
+
+ void setupDynamicShadowZ(Options options, Context context) {
+ if (options.getDynamicShadowUnfocusedZ() < 0f) {
+ Resources res = context.getResources();
+ mFocusedZ = res.getDimension(R.dimen.lb_material_shadow_focused_z);
+ mUnfocusedZ = res.getDimension(R.dimen.lb_material_shadow_normal_z);
+ } else {
+ mFocusedZ = options.getDynamicShadowFocusedZ();
+ mUnfocusedZ = options.getDynamicShadowUnfocusedZ();
+ }
+ }
+
+ void setupRoundedCornerRadius(Options options, Context context) {
+ if (options.getRoundedCornerRadius() == 0) {
+ Resources res = context.getResources();
+ mRoundedCornerRadius = res.getDimensionPixelSize(
+ R.dimen.lb_rounded_rect_corner_radius);
+ } else {
+ mRoundedCornerRadius = options.getRoundedCornerRadius();
+ }
+ }
+
+ static Object getNoneWrapperDyamicShadowImpl(View view) {
+ return view.getTag(R.id.lb_shadow_impl);
+ }
+
+ static void setShadowFocusLevel(Object impl, int shadowType, float level) {
+ if (impl != null) {
+ if (level < 0f) {
+ level = 0f;
+ } else if (level > 1f) {
+ level = 1f;
+ }
+ switch (shadowType) {
+ case SHADOW_DYNAMIC:
+ ShadowHelper.getInstance().setShadowFocusLevel(impl, level);
+ break;
+ case SHADOW_STATIC:
+ StaticShadowHelper.getInstance().setShadowFocusLevel(impl, level);
+ break;
+ }
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
index 4d8411b..022ff1d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
@@ -34,7 +34,7 @@
*/
static interface ShadowHelperVersionImpl {
public void prepareParent(ViewGroup parent);
- public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners);
+ public Object addStaticShadow(ViewGroup shadowContainer);
public void setShadowFocusLevel(Object impl, float level);
}
@@ -48,7 +48,7 @@
}
@Override
- public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners) {
+ public Object addStaticShadow(ViewGroup shadowContainer) {
// do nothing
return null;
}
@@ -69,8 +69,7 @@
}
@Override
- public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners) {
- // Static shadows are always rounded
+ public Object addStaticShadow(ViewGroup shadowContainer) {
return ShadowHelperJbmr2.addShadow(shadowContainer);
}
@@ -105,8 +104,8 @@
mImpl.prepareParent(parent);
}
- public Object addStaticShadow(ViewGroup shadowContainer, boolean roundedCorners) {
- return mImpl.addStaticShadow(shadowContainer, roundedCorners);
+ public Object addStaticShadow(ViewGroup shadowContainer) {
+ return mImpl.addStaticShadow(shadowContainer);
}
public void setShadowFocusLevel(Object impl, float level) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
index 16b66cd..f049105 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
@@ -31,6 +31,13 @@
class VerticalGridItemBridgeAdapter extends ItemBridgeAdapter {
@Override
+ protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ if (mShadowOverlayHelper != null) {
+ mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
+ }
+ }
+
+ @Override
public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
// Only when having an OnItemClickListner, we attach the OnClickListener.
if (getOnItemViewClickedListener() != null) {
@@ -83,9 +90,12 @@
private int mFocusZoomFactor;
private boolean mUseFocusDimmer;
private boolean mShadowEnabled = true;
+ private boolean mKeepChildForeground = true;
private OnItemViewSelectedListener mOnItemViewSelectedListener;
private OnItemViewClickedListener mOnItemViewClickedListener;
private boolean mRoundedCornersEnabled = true;
+ private ShadowOverlayHelper mShadowOverlayHelper;
+ private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper;
/**
* Constructs a VerticalGridPresenter with defaults.
@@ -170,7 +180,7 @@
* Subclass may return false to disable.
*/
public boolean isUsingDefaultShadow() {
- return ShadowOverlayContainer.supportsShadow();
+ return ShadowOverlayHelper.supportsShadow();
}
/**
@@ -194,8 +204,7 @@
* and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
*/
public boolean isUsingZOrder(Context context) {
- return ShadowOverlayContainer.supportsDynamicShadow() &&
- !Settings.getInstance(context).preferStaticShadows();
+ return !Settings.getInstance(context).preferStaticShadows();
}
final boolean needsDefaultShadow() {
@@ -216,7 +225,6 @@
return mUseFocusDimmer;
}
-
@Override
public final ViewHolder onCreateViewHolder(ViewGroup parent) {
ViewHolder vh = createGridViewHolder(parent);
@@ -238,21 +246,6 @@
return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid));
}
- private ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
- @Override
- public View createWrapper(View root) {
- ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
- wrapper.setLayoutParams(
- new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
- wrapper.initialize(needsDefaultShadow(), true, areChildRoundedCornersEnabled());
- return wrapper;
- }
- @Override
- public void wrap(View wrapper, View wrapped) {
- ((ShadowOverlayContainer) wrapper).wrap(wrapped);
- }
- };
-
/**
* Called after a {@link VerticalGridPresenter.ViewHolder} is created.
* Subclasses may override this method and start by calling
@@ -268,12 +261,25 @@
vh.getGridView().setNumColumns(mNumColumns);
vh.mInitialized = true;
- vh.mItemBridgeAdapter.setWrapper(mWrapper);
- if (needsDefaultShadow() || areChildRoundedCornersEnabled()) {
- ShadowOverlayContainer.prepareParentForShadow(vh.getGridView());
- ((ViewGroup) vh.view).setClipChildren(false);
+ Context context = vh.mGridView.getContext();
+ if (mShadowOverlayHelper == null) {
+ mShadowOverlayHelper = new ShadowOverlayHelper.Builder()
+ .needsOverlay(mUseFocusDimmer)
+ .needsShadow(needsDefaultShadow())
+ .needsRoundedCorner(areChildRoundedCornersEnabled())
+ .preferZOrder(isUsingZOrder(context))
+ .keepForegroundDrawable(mKeepChildForeground)
+ .options(createShadowOverlayOptions())
+ .build(context);
+ if (mShadowOverlayHelper.needsWrapper()) {
+ mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper(
+ mShadowOverlayHelper);
+ }
}
- vh.getGridView().setFocusDrawingOrderEnabled(!isUsingZOrder(vh.getGridView().getContext()));
+ vh.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper);
+ mShadowOverlayHelper.prepareParentForShadow(vh.mGridView);
+ vh.getGridView().setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
+ == ShadowOverlayHelper.SHADOW_STATIC);
FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter,
mFocusZoomFactor, mUseFocusDimmer);
@@ -286,6 +292,39 @@
});
}
+ /**
+ * Set if keeps foreground of child of this grid, the foreground will not
+ * be used for overlay color. Default value is true.
+ *
+ * @param keep True if keep foreground of child of this grid.
+ */
+ public final void setKeepChildForeground(boolean keep) {
+ mKeepChildForeground = keep;
+ }
+
+ /**
+ * Returns true if keeps foreground of child of this grid, the foreground will not
+ * be used for overlay color. Default value is true.
+ *
+ * @return True if keeps foreground of child of this grid.
+ */
+ public final boolean getKeepChildForeground() {
+ return mKeepChildForeground;
+ }
+
+ /**
+ * Create ShadowOverlayHelper Options. Subclass may override.
+ * e.g.
+ * <code>
+ * return new ShadowOverlayHelper.Options().roundedCornerRadius(10);
+ * </code>
+ *
+ * @return The options to be used for shadow, overlay and rouded corner.
+ */
+ protected ShadowOverlayHelper.Options createShadowOverlayOptions() {
+ return ShadowOverlayHelper.Options.DEFAULT;
+ }
+
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
if (DEBUG) Log.v(TAG, "onBindViewHolder " + item);
@@ -345,4 +384,18 @@
}
}
}
+
+ /**
+ * Changes the visibility of views. The entrance transition will be run against the views that
+ * change visibilities. This method is called by the fragment, it should not be called
+ * directly by the application.
+ *
+ * @param holder The ViewHolder for the vertical grid.
+ * @param afterEntrance true if children of vertical grid participating in entrance transition
+ * should be set to visible, false otherwise.
+ */
+ public void setEntranceTransitionState(VerticalGridPresenter.ViewHolder holder,
+ boolean afterEntrance) {
+ holder.mGridView.setChildrenVisibility(afterEntrance? View.VISIBLE : View.INVISIBLE);
+ }
}
diff --git a/v17/preference-leanback/res/color/lb_preference_item_primary_text_color.xml b/v17/preference-leanback/res/color/lb_preference_item_primary_text_color.xml
new file mode 100644
index 0000000..efdf1c0
--- /dev/null
+++ b/v17/preference-leanback/res/color/lb_preference_item_primary_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/lb_preference_item_primary_text_color_disabled" />
+ <item android:color="@color/lb_preference_item_primary_text_color_default"/>
+</selector>
diff --git a/v17/preference-leanback/res/color/lb_preference_item_secondary_text_color.xml b/v17/preference-leanback/res/color/lb_preference_item_secondary_text_color.xml
new file mode 100644
index 0000000..68bb81a
--- /dev/null
+++ b/v17/preference-leanback/res/color/lb_preference_item_secondary_text_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/lb_preference_item_secondary_text_color_disabled" />
+ <item android:color="@color/lb_preference_item_secondary_text_color_default"/>
+</selector>
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_fragment.xml b/v17/preference-leanback/res/layout/leanback_list_preference_fragment.xml
index 9fae0f8..2a4218b 100644
--- a/v17/preference-leanback/res/layout/leanback_list_preference_fragment.xml
+++ b/v17/preference-leanback/res/layout/leanback_list_preference_fragment.xml
@@ -35,11 +35,11 @@
android:id="@+id/decor_title"
android:layout_width="match_parent"
android:layout_height="@dimen/lb_preference_decor_title_text_height"
+ android:layout_marginTop="@dimen/lb_preference_decor_title_margin_top"
+ android:layout_marginStart="@dimen/lb_preference_decor_title_margin_start"
+ android:layout_marginEnd="@dimen/lb_preference_decor_title_margin_end"
android:fontFamily="sans-serif-condensed"
android:gravity="center_vertical"
- android:paddingTop="@dimen/lb_preference_decor_title_padding_top"
- android:paddingStart="@dimen/lb_preference_decor_title_padding_start"
- android:paddingEnd="@dimen/lb_preference_decor_title_padding_end"
android:singleLine="true"
android:textSize="@dimen/lb_preference_decor_title_text_size"
android:textColor="?android:attr/textColorPrimary"
@@ -50,6 +50,7 @@
android:id="@android:id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:fontFamily="sans-serif-condensed"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:paddingStart="24dp"
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml b/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
index 3b1345c..728ecff 100644
--- a/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
+++ b/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
@@ -18,20 +18,35 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:descendantFocusability="blocksDescendants"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/lb_preference_item_padding_start"
+ android:paddingEnd="@dimen/lb_preference_item_padding_end" >
+
<CheckBox
android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@android:id/title"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center_vertical" />
+ android:layout_width="@dimen/lb_preference_item_icon_size"
+ android:layout_height="@dimen/lb_preference_item_icon_size"
+ android:layout_marginEnd="@dimen/lb_preference_item_icon_margin_end"
+ android:layout_gravity="center_vertical" />
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_top" />
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/lb_preference_item_primary_text_margin_bottom"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="@color/lb_preference_item_primary_text_color"
+ android:textSize="@dimen/lb_preference_item_primary_text_size"/>
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_bottom" />
+ </LinearLayout>
+
</LinearLayout>
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml b/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
index eaf42a4..354ca41 100644
--- a/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
+++ b/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
@@ -18,20 +18,35 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
+ android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:descendantFocusability="blocksDescendants"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/lb_preference_item_padding_start"
+ android:paddingEnd="@dimen/lb_preference_item_padding_end" >
+
<RadioButton
android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@android:id/title"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center_vertical" />
+ android:layout_width="@dimen/lb_preference_item_icon_size"
+ android:layout_height="@dimen/lb_preference_item_icon_size"
+ android:layout_marginEnd="@dimen/lb_preference_item_icon_margin_end"
+ android:layout_gravity="center_vertical" />
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_top" />
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/lb_preference_item_primary_text_margin_bottom"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="@color/lb_preference_item_primary_text_color"
+ android:textSize="@dimen/lb_preference_item_primary_text_size"/>
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_bottom" />
+ </LinearLayout>
+
</LinearLayout>
diff --git a/v17/preference-leanback/res/layout/leanback_preference.xml b/v17/preference-leanback/res/layout/leanback_preference.xml
new file mode 100644
index 0000000..85baff3
--- /dev/null
+++ b/v17/preference-leanback/res/layout/leanback_preference.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:descendantFocusability="blocksDescendants"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/lb_preference_item_padding_start"
+ android:paddingEnd="@dimen/lb_preference_item_padding_end" >
+
+ <FrameLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" >
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ />
+ </FrameLayout>
+
+ <LinearLayout android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_top" />
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/lb_preference_item_primary_text_margin_bottom"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="@color/lb_preference_item_primary_text_color"
+ android:textSize="@dimen/lb_preference_item_primary_text_size"/>
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="@color/lb_preference_item_secondary_text_color"
+ android:textSize="@dimen/lb_preference_item_secondary_text_size"
+ android:maxLines="4" />
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_bottom" />
+ </LinearLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/v17/preference-leanback/res/layout/leanback_preference_category.xml b/v17/preference-leanback/res/layout/leanback_preference_category.xml
new file mode 100644
index 0000000..9b978f3
--- /dev/null
+++ b/v17/preference-leanback/res/layout/leanback_preference_category.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/lb_preference_category_height"
+ android:clipToPadding="false"
+ android:paddingStart="@dimen/lb_preference_item_padding_start"
+ android:paddingEnd="@dimen/lb_preference_item_padding_end">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="?android:attr/colorAccent"
+ android:textSize="@dimen/lb_preference_category_text_size"/>
+</FrameLayout>
diff --git a/v17/preference-leanback/res/layout/leanback_preference_fragment.xml b/v17/preference-leanback/res/layout/leanback_preference_fragment.xml
index d119c2d..199e0f7 100644
--- a/v17/preference-leanback/res/layout/leanback_preference_fragment.xml
+++ b/v17/preference-leanback/res/layout/leanback_preference_fragment.xml
@@ -20,6 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/lb_preference_decor_list_background"
+ android:elevation="@dimen/lb_preference_decor_elevation"
android:orientation="vertical"
android:transitionGroup="false"
>
@@ -29,17 +30,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/defaultBrandColor"
+ android:elevation="@dimen/lb_preference_decor_title_container_elevation"
android:transitionGroup="false"
>
<TextView
android:id="@+id/decor_title"
android:layout_width="match_parent"
android:layout_height="@dimen/lb_preference_decor_title_text_height"
+ android:layout_marginTop="@dimen/lb_preference_decor_title_margin_top"
+ android:layout_marginStart="@dimen/lb_preference_decor_title_margin_start"
+ android:layout_marginEnd="@dimen/lb_preference_decor_title_margin_end"
android:fontFamily="sans-serif-condensed"
android:gravity="center_vertical"
- android:paddingTop="@dimen/lb_preference_decor_title_padding_top"
- android:paddingStart="@dimen/lb_preference_decor_title_padding_start"
- android:paddingEnd="@dimen/lb_preference_decor_title_padding_end"
android:singleLine="true"
android:textSize="@dimen/lb_preference_decor_title_text_size"
android:textColor="?android:attr/textColorPrimary"
diff --git a/v17/preference-leanback/res/layout/leanback_preference_information.xml b/v17/preference-leanback/res/layout/leanback_preference_information.xml
new file mode 100644
index 0000000..18da8d9
--- /dev/null
+++ b/v17/preference-leanback/res/layout/leanback_preference_information.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="false"
+ android:focusable="false"
+ android:descendantFocusability="blocksDescendants"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/lb_preference_item_padding_start"
+ android:paddingEnd="@dimen/lb_preference_item_padding_end" >
+
+ <LinearLayout android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_top" />
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/lb_preference_item_primary_text_margin_bottom"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="@color/lb_preference_item_primary_text_color"
+ android:textSize="@dimen/lb_preference_item_primary_text_size"/>
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="sans-serif-condensed"
+ android:textColor="@color/lb_preference_item_secondary_text_color"
+ android:textSize="@dimen/lb_preference_item_secondary_text_size"
+ android:maxLines="4" />
+ <Space android:layout_width="0dp" android:layout_height="@dimen/lb_preference_item_text_space_bottom" />
+ </LinearLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/v17/preference-leanback/res/values/colors.xml b/v17/preference-leanback/res/values/colors.xml
index de6c888..30a373a 100644
--- a/v17/preference-leanback/res/values/colors.xml
+++ b/v17/preference-leanback/res/values/colors.xml
@@ -16,4 +16,11 @@
-->
<resources>
<color name="lb_preference_decor_list_background">#263238</color>
+
+ <color name="lb_preference_item_primary_text_color_default">#EEEEEE</color>
+ <color name="lb_preference_item_primary_text_color_disabled">#4DEEEEEE</color>
+
+ <color name="lb_preference_item_secondary_text_color_default">#B3EEEEEE</color>
+ <color name="lb_preference_item_secondary_text_color_disabled">#4DEEEEEE</color>
+
</resources>
diff --git a/v17/preference-leanback/res/values/dimens.xml b/v17/preference-leanback/res/values/dimens.xml
index 49763fe..f3d36af 100644
--- a/v17/preference-leanback/res/values/dimens.xml
+++ b/v17/preference-leanback/res/values/dimens.xml
@@ -15,11 +15,27 @@
~ limitations under the License
-->
<resources>
- <dimen name="lb_preference_decor_title_text_height">64dp</dimen>
- <dimen name="lb_preference_decor_title_padding_top">27dp</dimen>
- <dimen name="lb_preference_decor_title_padding_start">24dp</dimen>
- <dimen name="lb_preference_decor_title_padding_end">56dp</dimen>
- <dimen name="lb_preference_decor_title_text_size">20sp</dimen>
+ <dimen name="lb_preference_decor_title_text_height">64dp</dimen>
+ <dimen name="lb_preference_decor_title_margin_top">27dp</dimen>
+ <dimen name="lb_preference_decor_title_margin_start">24dp</dimen>
+ <dimen name="lb_preference_decor_title_margin_end">56dp</dimen>
+ <dimen name="lb_preference_decor_title_text_size">20sp</dimen>
+ <dimen name="lb_preference_decor_title_container_elevation">2dp</dimen>
+ <dimen name="lb_preference_decor_elevation">6dp</dimen>
- <dimen name="lb_settings_pane_width">360dp</dimen>
+ <dimen name="lb_preference_item_padding_start">24dp</dimen>
+ <dimen name="lb_preference_item_padding_end">56dp</dimen>
+ <dimen name="lb_preference_item_icon_size">32dp</dimen>
+ <dimen name="lb_preference_item_icon_margin_end">16dp</dimen>
+
+ <dimen name="lb_preference_item_primary_text_size">14sp</dimen>
+ <dimen name="lb_preference_item_primary_text_margin_bottom">2dp</dimen>
+ <dimen name="lb_preference_item_secondary_text_size">12sp</dimen>
+ <dimen name="lb_preference_item_text_space_top">14dp</dimen>
+ <dimen name="lb_preference_item_text_space_bottom">13dp</dimen>
+
+ <dimen name="lb_preference_category_text_size">12sp</dimen>
+ <dimen name="lb_preference_category_height">40dp</dimen>
+
+ <dimen name="lb_settings_pane_width">360dp</dimen>
</resources>
diff --git a/v17/preference-leanback/res/values/styles.xml b/v17/preference-leanback/res/values/styles.xml
new file mode 100644
index 0000000..0b315a8
--- /dev/null
+++ b/v17/preference-leanback/res/values/styles.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<resources>
+
+ <style name="LeanbackPreference">
+ <item name="layout">@layout/leanback_preference</item>
+ </style>
+
+ <style name="LeanbackPreference.Information">
+ <item name="layout">@layout/leanback_preference_information</item>
+ <item name="enabled">false</item>
+ <item name="shouldDisableView">false</item>
+ </style>
+
+ <style name="LeanbackPreference.Category">
+ <item name="layout">@layout/leanback_preference_category</item>
+ <!-- The title should not dim if the category is disabled, instead only the preference children should dim. -->
+ <item name="shouldDisableView">false</item>
+ <item name="selectable">false</item>
+ </style>
+
+ <style name="LeanbackPreference.CheckBoxPreference">
+ <item name="widgetLayout">@layout/preference_widget_checkbox</item>
+ </style>
+
+ <style name="LeanbackPreference.SwitchPreferenceCompat">
+ <item name="widgetLayout">@layout/preference_widget_switch_compat</item>
+ <item name="switchTextOn">@string/v7_preference_on</item>
+ <item name="switchTextOff">@string/v7_preference_off</item>
+ </style>
+
+ <style name="LeanbackPreference.SwitchPreference">
+ <item name="widgetLayout">@layout/preference_widget_switch</item>
+ <item name="switchTextOn">@string/v7_preference_on</item>
+ <item name="switchTextOff">@string/v7_preference_off</item>
+ </style>
+
+ <style name="LeanbackPreference.PreferenceScreen">
+ </style>
+
+ <style name="LeanbackPreference.DialogPreference">
+ <item name="positiveButtonText">@android:string/ok</item>
+ <item name="negativeButtonText">@android:string/cancel</item>
+ </style>
+
+ <style name="LeanbackPreference.DialogPreference.EditTextPreference">
+ <item name="dialogLayout">@layout/preference_dialog_edittext</item>
+ </style>
+
+</resources>
diff --git a/v17/preference-leanback/res/values/themes.xml b/v17/preference-leanback/res/values/themes.xml
new file mode 100644
index 0000000..b3060d9
--- /dev/null
+++ b/v17/preference-leanback/res/values/themes.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <style name="PreferenceThemeOverlay.v14.Leanback">
+ <item name="preferenceScreenStyle">@style/LeanbackPreference.PreferenceScreen</item>
+ <item name="preferenceCategoryStyle">@style/LeanbackPreference.Category</item>
+ <item name="preferenceStyle">@style/LeanbackPreference</item>
+ <item name="preferenceInformationStyle">@style/LeanbackPreference.Information</item>
+ <item name="checkBoxPreferenceStyle">@style/LeanbackPreference.CheckBoxPreference</item>
+ <item name="switchPreferenceCompatStyle">@style/LeanbackPreference.SwitchPreferenceCompat</item>
+ <item name="switchPreferenceStyle">@style/LeanbackPreference.SwitchPreference</item>
+ <item name="dialogPreferenceStyle">@style/LeanbackPreference.DialogPreference</item>
+ <item name="editTextPreferenceStyle">@style/LeanbackPreference.DialogPreference.EditTextPreference</item>
+ </style>
+</resources>
diff --git a/v4/api/current.txt b/v4/api/current.txt
index 3c4290e..5e5eb1a 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -1449,6 +1449,12 @@
package android.support.v4.media.session {
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.view.KeyEvent handleIntent(android.support.v4.media.session.MediaSessionCompat, android.content.Intent);
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
public final class MediaControllerCompat {
ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat);
ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
@@ -1516,6 +1522,7 @@
}
public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, java.lang.String);
ctor public MediaSessionCompat(android.content.Context, java.lang.String, android.content.ComponentName, android.app.PendingIntent);
method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener);
method public android.support.v4.media.session.MediaControllerCompat getController();
@@ -2406,6 +2413,7 @@
method public static float getZ(android.view.View);
method public static boolean hasAccessibilityDelegate(android.view.View);
method public static boolean hasNestedScrollingParent(android.view.View);
+ method public static boolean hasOnClickListeners(android.view.View);
method public static boolean hasOverlappingRendering(android.view.View);
method public static boolean hasTransientState(android.view.View);
method public static boolean isAttachedToWindow(android.view.View);
@@ -3272,6 +3280,7 @@
method public boolean isOverScrolled();
method public void notifyHorizontalEdgeReached(int, int, int);
method public void notifyVerticalEdgeReached(int, int, int);
+ method public boolean springBack(int, int, int, int, int, int);
method public void startScroll(int, int, int, int);
method public void startScroll(int, int, int, int, int);
}
diff --git a/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java b/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
index 991515a..234a77a 100644
--- a/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
@@ -21,7 +21,7 @@
import android.os.Bundle;
import android.os.Parcel;
-public class MediaDescriptionCompatApi21 {
+class MediaDescriptionCompatApi21 {
public static String getMediaId(Object descriptionObj) {
return ((MediaDescription) descriptionObj).getMediaId();
@@ -59,7 +59,7 @@
return MediaDescription.CREATOR.createFromParcel(in);
}
- public static class Builder {
+ static class Builder {
public static Object newInstance() {
return new MediaDescription.Builder();
}
diff --git a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
index 8094391..b3e7fd1 100644
--- a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
+++ b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
@@ -132,7 +132,7 @@
((MediaSession) sessionObj).setExtras(extras);
}
- public static interface Callback {
+ static interface Callback {
public void onCommand(String command, Bundle extras, ResultReceiver cb);
public boolean onMediaButtonEvent(Intent mediaButtonIntent);
public void onPlay();
diff --git a/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java b/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
index f49eb2b..d03287fe 100644
--- a/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
+++ b/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.media.AudioManager;
-public class MediaSessionCompatApi8 {
+class MediaSessionCompatApi8 {
public static void registerMediaButtonEventReceiver(Context context, ComponentName mbr) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
am.registerMediaButtonEventReceiver(mbr);
diff --git a/v4/gingerbread/android/support/v4/widget/ScrollerCompatGingerbread.java b/v4/gingerbread/android/support/v4/widget/ScrollerCompatGingerbread.java
index 429e864..61c9a03 100644
--- a/v4/gingerbread/android/support/v4/widget/ScrollerCompatGingerbread.java
+++ b/v4/gingerbread/android/support/v4/widget/ScrollerCompatGingerbread.java
@@ -87,4 +87,9 @@
public static int getFinalY(Object scroller) {
return ((OverScroller) scroller).getFinalY();
}
+
+ public static boolean springBack(Object scroller, int startX, int startY, int minX, int maxX,
+ int minY, int maxY) {
+ return ((OverScroller) scroller).springBack(startX, startY, minX, maxX, minY, maxY);
+ }
}
diff --git a/v4/ics-mr1/android/support/v4/view/ViewCompatICSMr1.java b/v4/ics-mr1/android/support/v4/view/ViewCompatICSMr1.java
new file mode 100644
index 0000000..780345c
--- /dev/null
+++ b/v4/ics-mr1/android/support/v4/view/ViewCompatICSMr1.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.view;
+
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Helper for accessing newer features in View introduced in ICS Mr1.
+ */
+class ViewCompatICSMr1 {
+ public static boolean hasOnClickListeners(View v) {
+ return v.hasOnClickListeners();
+ }
+}
diff --git a/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
index de644e6..3378e1e 100644
--- a/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
+++ b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
@@ -25,7 +25,7 @@
import android.os.Bundle;
import android.os.ResultReceiver;
-public class MediaSessionCompatApi14 {
+class MediaSessionCompatApi14 {
/***** RemoteControlClient States, we only need none as the others were public *******/
final static int RCC_PLAYSTATE_NONE = 0;
@@ -224,7 +224,7 @@
}
}
- public static interface Callback {
+ static interface Callback {
public void onCommand(String command, Bundle extras, ResultReceiver cb);
public boolean onMediaButtonEvent(Intent mediaButtonIntent);
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index 866bb97..fc6a776 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -52,6 +52,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -414,8 +415,9 @@
static class AnimateOnHWLayerIfNeededListener implements AnimationListener {
+ private AnimationListener mOrignalListener = null;
private boolean mShouldRunOnHWLayer = false;
- private View mView;
+ private View mView = null;
public AnimateOnHWLayerIfNeededListener(final View v, Animation anim) {
if (v == null || anim == null) {
return;
@@ -423,25 +425,45 @@
mView = v;
}
+ public AnimateOnHWLayerIfNeededListener(final View v, Animation anim,
+ AnimationListener listener) {
+ if (v == null || anim == null) {
+ return;
+ }
+ mOrignalListener = listener;
+ mView = v;
+ }
+
@Override
@CallSuper
public void onAnimationStart(Animation animation) {
- mShouldRunOnHWLayer = shouldRunOnHWLayer(mView, animation);
- if (mShouldRunOnHWLayer) {
- ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_HARDWARE, null);
+ if (mView != null) {
+ mShouldRunOnHWLayer = shouldRunOnHWLayer(mView, animation);
+ if (mShouldRunOnHWLayer) {
+ ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_HARDWARE, null);
+ }
+ }
+ if (mOrignalListener != null) {
+ mOrignalListener.onAnimationStart(animation);
}
}
@Override
@CallSuper
public void onAnimationEnd(Animation animation) {
- if (mShouldRunOnHWLayer) {
+ if (mView != null && mShouldRunOnHWLayer) {
ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_NONE, null);
}
+ if (mOrignalListener != null) {
+ mOrignalListener.onAnimationEnd(animation);
+ }
}
@Override
public void onAnimationRepeat(Animation animation) {
+ if (mOrignalListener != null) {
+ mOrignalListener.onAnimationRepeat(animation);
+ }
}
}
@@ -466,6 +488,8 @@
FragmentController mController;
FragmentContainer mContainer;
Fragment mParent;
+
+ static Field sAnimationListenerField = null;
boolean mNeedMenuInvalidate;
boolean mStateSaved;
@@ -906,7 +930,23 @@
return;
}
if (shouldRunOnHWLayer(v, anim)) {
- anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(v, anim));
+ AnimationListener originalListener = null;
+ try {
+ if (sAnimationListenerField == null) {
+ sAnimationListenerField = Animation.class.getDeclaredField("mListener");
+ sAnimationListenerField.setAccessible(true);
+ }
+ originalListener = (AnimationListener) sAnimationListenerField.get(anim);
+ } catch (NoSuchFieldException e) {
+ Log.e(TAG, "No field with the name mListener is found in Animation class", e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Cannot access Animation's mListener field", e);
+ }
+ // If there's already a listener set on the animation, we need wrap the new listener
+ // around the existing listener, so that they will both get animation listener
+ // callbacks.
+ anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(v, anim,
+ originalListener));
}
}
diff --git a/v4/java/android/support/v4/graphics/ColorUtils.java b/v4/java/android/support/v4/graphics/ColorUtils.java
index aac809bc..4d9d9b2 100644
--- a/v4/java/android/support/v4/graphics/ColorUtils.java
+++ b/v4/java/android/support/v4/graphics/ColorUtils.java
@@ -17,6 +17,10 @@
package android.support.v4.graphics;
import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.FloatRange;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
/**
* A set of color-related utility methods, building upon those available in {@code Color}.
@@ -24,14 +28,14 @@
public class ColorUtils {
private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
- private static final int MIN_ALPHA_SEARCH_PRECISION = 10;
+ private static final int MIN_ALPHA_SEARCH_PRECISION = 1;
private ColorUtils() {}
/**
* Composite two potentially translucent colors over each other and returns the result.
*/
- public static int compositeColors(int foreground, int background) {
+ public static int compositeColors(@ColorInt int foreground, @ColorInt int background) {
int bgAlpha = Color.alpha(background);
int fgAlpha = Color.alpha(foreground);
int a = compositeAlpha(fgAlpha, bgAlpha);
@@ -57,10 +61,13 @@
/**
* Returns the luminance of a color.
- *
- * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+ * <p>
+ * Formula defined
+ * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef">here</a>.
+ * </p>
*/
- public static double calculateLuminance(int color) {
+ @FloatRange(from = 0.0, to = 1.0)
+ public static double calculateLuminance(@ColorInt int color) {
double red = Color.red(color) / 255d;
red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
@@ -80,9 +87,10 @@
* Formula defined
* <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
*/
- public static double calculateContrast(int foreground, int background) {
+ public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) {
if (Color.alpha(background) != 255) {
- throw new IllegalArgumentException("background can not be translucent");
+ throw new IllegalArgumentException("background can not be translucent: #"
+ + Integer.toHexString(background));
}
if (Color.alpha(foreground) < 255) {
// If the foreground is translucent, composite the foreground over the background
@@ -106,10 +114,11 @@
* @param minContrastRatio the minimum contrast ratio.
* @return the alpha value in the range 0-255, or -1 if no value could be calculated.
*/
- public static int calculateMinimumAlpha(int foreground, int background,
+ public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
float minContrastRatio) {
if (Color.alpha(background) != 255) {
- throw new IllegalArgumentException("background can not be translucent");
+ throw new IllegalArgumentException("background can not be translucent: #"
+ + Integer.toHexString(background));
}
// First lets check that a fully opaque foreground has sufficient contrast
@@ -158,7 +167,9 @@
* @param b blue component value [0..255]
* @param hsl 3 element array which holds the resulting HSL components.
*/
- public static void RGBToHSL(int r, int g, int b, float[] hsl) {
+ public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull float[] hsl) {
final float rf = r / 255f;
final float gf = g / 255f;
final float bf = b / 255f;
@@ -206,7 +217,7 @@
* @param color the ARGB color to convert. The alpha component is ignored.
* @param hsl 3 element array which holds the resulting HSL components.
*/
- public static void colorToHSL(int color, float[] hsl) {
+ public static void colorToHSL(@ColorInt int color, @NonNull float[] hsl) {
RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
}
@@ -222,7 +233,8 @@
* @param hsl 3 element array which holds the input HSL components.
* @return the resulting RGB color
*/
- public static int HSLToColor(float[] hsl) {
+ @ColorInt
+ public static int HSLToColor(@NonNull float[] hsl) {
final float h = hsl[0];
final float s = hsl[1];
final float l = hsl[2];
@@ -279,7 +291,9 @@
/**
* Set the alpha component of {@code color} to be {@code alpha}.
*/
- public static int setAlphaComponent(int color, int alpha) {
+ @ColorInt
+ public static int setAlphaComponent(@ColorInt int color,
+ @IntRange(from = 0x0, to = 0xFF) int alpha) {
if (alpha < 0 || alpha > 255) {
throw new IllegalArgumentException("alpha must be between 0 and 255.");
}
diff --git a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
index 88b5f4a..64ae075 100644
--- a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
+++ b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
@@ -123,7 +123,7 @@
@Override
public int getLayoutDirection(Drawable drawable) {
final int dir = DrawableCompatJellybeanMr1.getLayoutDirection(drawable);
- return dir < 0 ? dir : ViewCompat.LAYOUT_DIRECTION_LTR;
+ return dir >= 0 ? dir : ViewCompat.LAYOUT_DIRECTION_LTR;
}
}
diff --git a/v4/java/android/support/v4/media/session/MediaButtonReceiver.java b/v4/java/android/support/v4/media/session/MediaButtonReceiver.java
new file mode 100644
index 0000000..da2ce08
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/MediaButtonReceiver.java
@@ -0,0 +1,129 @@
+/*
+ * 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.v4.media.session;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.view.KeyEvent;
+
+import java.util.List;
+
+/**
+ * A media button receiver receives and helps translate hardware media playback buttons,
+ * such as those found on wired and wireless headsets, into the appropriate callbacks
+ * in your app.
+ * <p />
+ * You can add this MediaButtonReceiver to your app by adding it directly to your
+ * AndroidManifest.xml:
+ * <pre>
+ * <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
+ * <intent-filter>
+ * <action android:name="android.intent.action.MEDIA_BUTTON" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ * This class assumes you have a {@link Service} in your app that controls
+ * media playback via a {@link MediaSessionCompat}. That {@link Service} must
+ * include an intent filter that also handles {@link Intent#ACTION_MEDIA_BUTTON}:
+ * <pre>
+ * <service android:name="com.example.android.MediaPlaybackService" >
+ * <intent-filter>
+ * <action android:name="android.intent.action.MEDIA_BUTTON" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ *
+ * All {@link Intent}s sent to this MediaButtonReceiver will then be forwarded
+ * to the {@link Service}. Events can then be handled in
+ * {@link Service#onStartCommand(Intent, int, int)} by calling
+ * {@link MediaButtonReceiver#handleIntent(MediaSessionCompat, Intent)}, passing in
+ * your current {@link MediaSessionCompat}:
+ * <pre>
+ * private MediaSessionCompat mMediaSessionCompat = ...;
+ *
+ * public int onStartCommand(Intent intent, int flags, int startId) {
+ * MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent);
+ * return super.onStartCommand(intent, flags, startId);
+ * }
+ * </pre>
+ *
+ * This ensures that the correct callbacks to {@link MediaSessionCompat.Callback}
+ * will be triggered based on the incoming {@link KeyEvent}.
+ */
+public class MediaButtonReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ queryIntent.setPackage(context.getPackageName());
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent, 0);
+ if (resolveInfos.size() != 1) {
+ throw new IllegalStateException("Expected 1 Service that handles " +
+ Intent.ACTION_MEDIA_BUTTON + ", found " + resolveInfos.size());
+ }
+ ResolveInfo resolveInfo = resolveInfos.get(0);
+ ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ intent.setComponent(componentName);
+ context.startService(intent);
+ }
+
+ /**
+ * Extracts any available {@link KeyEvent} from an {@link Intent#ACTION_MEDIA_BUTTON}
+ * intent, passing it onto the {@link MediaSessionCompat} using
+ * {@link MediaControllerCompat#dispatchMediaButtonEvent(KeyEvent)}, which in turn
+ * will trigger callbacks to the {@link MediaSessionCompat.Callback} registered via
+ * {@link MediaSessionCompat#setCallback(MediaSessionCompat.Callback)}.
+ * <p />
+ * The returned {@link KeyEvent} is non-null if any {@link KeyEvent} is found and can
+ * be used if any additional processing is needed beyond what is done in the
+ * {@link MediaSessionCompat.Callback}. An example of is to prevent redelivery of a
+ * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE} Intent in the case of the Service being
+ * restarted (which, by default, will redeliver the last received Intent).
+ * <pre>
+ * KeyEvent keyEvent = MediaButtonReceiver.handleIntent(mediaSession, intent);
+ * if (keyEvent != null && keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+ * Intent emptyIntent = new Intent(intent);
+ * emptyIntent.setAction("");
+ * startService(emptyIntent);
+ * }
+ * </pre>
+ * @param mediaSessionCompat A {@link MediaSessionCompat} that has a
+ * {@link MediaSessionCompat.Callback} set
+ * @param intent The intent to parse
+ * @return The extracted {@link KeyEvent} if found, or null.
+ */
+ public static KeyEvent handleIntent(MediaSessionCompat mediaSessionCompat, Intent intent) {
+ if (mediaSessionCompat == null || intent == null
+ || !Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
+ || !intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
+ return null;
+ }
+ KeyEvent ke = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ MediaControllerCompat mediaController = mediaSessionCompat.getController();
+ mediaController.dispatchMediaButtonEvent(ke);
+ return ke;
+ }
+}
+
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.java b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
index fa963df..6d3cb27 100644
--- a/v4/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -19,9 +19,12 @@
import android.app.Activity;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
@@ -77,6 +80,8 @@
* backwards compatible fashion.
*/
public class MediaSessionCompat {
+ private static final String TAG = "MediaSessionCompat";
+
private final MediaSessionImpl mImpl;
private final MediaControllerCompat mController;
private final ArrayList<OnActiveChangeListener>
@@ -102,12 +107,27 @@
public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
/**
+ * Creates a new session using a media button receiver from your manifest.
+ * Note that a media button receiver is required to support platform versions
+ * earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+ *
+ * @param context The context.
+ * @param tag A short name for debugging purposes.
+ */
+ public MediaSessionCompat(Context context, String tag) {
+ this(context, tag, null, null);
+ }
+
+ /**
* Creates a new session.
*
* @param context The context.
* @param tag A short name for debugging purposes.
* @param mediaButtonEventReceiver The component name for your receiver.
- * This must be non-null to support platform versions earlier
+ * If null, this will attempt to find an appropriate
+ * {@link BroadcastReceiver} that handles
+ * {@link Intent#ACTION_MEDIA_BUTTON} from your manifest.
+ * A receiver is required to support platform versions earlier
* than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
* @param mbrIntent The PendingIntent for your receiver component that
* handles media button events. This is optional and will be used
@@ -123,6 +143,24 @@
throw new IllegalArgumentException("tag must not be null or empty");
}
+ if (mediaButtonEventReceiver == null) {
+ Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ queryIntent.setPackage(context.getPackageName());
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0);
+ // If none are found, assume we are running on a newer platform version that does
+ // not require a media button receiver ComponentName. Later code will double check
+ // this assumption and throw an error if needed
+ if (resolveInfos.size() == 1) {
+ ResolveInfo resolveInfo = resolveInfos.get(0);
+ mediaButtonEventReceiver = new ComponentName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name);
+ } else if (resolveInfos.size() > 1) {
+ Log.w(TAG, "More than one BroadcastReceiver that handles " +
+ Intent.ACTION_MEDIA_BUTTON + " was found, using null. Provide a " +
+ "specific ComponentName to use as this session's media button receiver");
+ }
+ }
if (mediaButtonEventReceiver != null && mbrIntent == null) {
// construct a PendingIntent for the media button
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
diff --git a/v4/java/android/support/v4/view/PagerTitleStrip.java b/v4/java/android/support/v4/view/PagerTitleStrip.java
index 72a83e6..6edbed8 100644
--- a/v4/java/android/support/v4/view/PagerTitleStrip.java
+++ b/v4/java/android/support/v4/view/PagerTitleStrip.java
@@ -423,35 +423,37 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Must measure with an exact width");
}
- int childHeight = heightSize;
- int minHeight = getMinHeight();
- int padding = 0;
- padding = getPaddingTop() + getPaddingBottom();
- childHeight -= padding;
+ final int heightPadding = getPaddingTop() + getPaddingBottom();
+ final int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,
+ heightPadding, LayoutParams.WRAP_CONTENT);
- final int maxWidth = Math.max(0, (int) (widthSize * 0.8f));
- final int childWidthSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
- final int maxHeight = Math.min(0, childHeight);
- final int childHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int widthPadding = (int) (widthSize * 0.2f);
+ final int childWidthSpec = getChildMeasureSpec(widthMeasureSpec,
+ widthPadding, LayoutParams.WRAP_CONTENT);
mPrevText.measure(childWidthSpec, childHeightSpec);
mCurrText.measure(childWidthSpec, childHeightSpec);
mNextText.measure(childWidthSpec, childHeightSpec);
+ final int height;
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
- setMeasuredDimension(widthSize, heightSize);
+ height = MeasureSpec.getSize(heightMeasureSpec);
} else {
- int textHeight = mCurrText.getMeasuredHeight();
- setMeasuredDimension(widthSize, Math.max(minHeight, textHeight + padding));
+ final int textHeight = mCurrText.getMeasuredHeight();
+ final int minHeight = getMinHeight();
+ height = Math.max(minHeight, textHeight + heightPadding);
}
+
+ final int childState = ViewCompat.getMeasuredState(mCurrText);
+ final int measuredHeight = ViewCompat.resolveSizeAndState(height, heightMeasureSpec,
+ childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
+ setMeasuredDimension(widthSize, measuredHeight);
}
@Override
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index d3dce89..4ae4910 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -384,6 +384,7 @@
int combineMeasuredStates(int curState, int newState);
public float getZ(View view);
public boolean isAttachedToWindow(View view);
+ public boolean hasOnClickListeners(View view);
}
static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -963,6 +964,11 @@
public boolean isAttachedToWindow(View view) {
return ViewCompatBase.isAttachedToWindow(view);
}
+
+ @Override
+ public boolean hasOnClickListeners(View view) {
+ return false;
+ }
}
static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -1222,7 +1228,14 @@
}
}
- static class JBViewCompatImpl extends ICSViewCompatImpl {
+ static class ICSMr1ViewCompatImpl extends ICSViewCompatImpl {
+ @Override
+ public boolean hasOnClickListeners(View view) {
+ return ViewCompatICSMr1.hasOnClickListeners(view);
+ }
+ }
+
+ static class JBViewCompatImpl extends ICSMr1ViewCompatImpl {
@Override
public boolean hasTransientState(View view) {
return ViewCompatJB.hasTransientState(view);
@@ -1540,6 +1553,8 @@
IMPL = new JbMr1ViewCompatImpl();
} else if (version >= 16) {
IMPL = new JBViewCompatImpl();
+ } else if (version >= 15) {
+ IMPL = new ICSMr1ViewCompatImpl();
} else if (version >= 14) {
IMPL = new ICSViewCompatImpl();
} else if (version >= 11) {
@@ -3085,4 +3100,13 @@
public static boolean isAttachedToWindow(View view) {
return IMPL.isAttachedToWindow(view);
}
+
+ /**
+ * Returns whether the provided view has an attached {@link View.OnClickListener}.
+ *
+ * @return true if there is a listener, false if there is none.
+ */
+ public static boolean hasOnClickListeners(View view) {
+ return IMPL.hasOnClickListeners(view);
+ }
}
diff --git a/v4/java/android/support/v4/widget/NestedScrollView.java b/v4/java/android/support/v4/widget/NestedScrollView.java
index 788761a..583c728 100644
--- a/v4/java/android/support/v4/widget/NestedScrollView.java
+++ b/v4/java/android/support/v4/widget/NestedScrollView.java
@@ -576,13 +576,6 @@
return true;
}
- /*
- * Don't try to intercept touch if we can't scroll anyway.
- */
- if (getScrollY() == 0 && !ViewCompat.canScrollVertically(this, 1)) {
- return false;
- }
-
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
@@ -657,6 +650,9 @@
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
+ if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
stopNestedScroll();
break;
case MotionEventCompat.ACTION_POINTER_UP:
@@ -795,6 +791,9 @@
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
flingWithNestedDispatch(-initialVelocity);
+ } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
+ getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
}
mActivePointerId = INVALID_POINTER;
@@ -803,6 +802,10 @@
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
+ if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
+ getScrollRange())) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
mActivePointerId = INVALID_POINTER;
endDrag();
}
@@ -942,6 +945,10 @@
clampedY = true;
}
+ if (clampedY) {
+ mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
+ }
+
onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
return clampedX || clampedY;
diff --git a/v4/java/android/support/v4/widget/ScrollerCompat.java b/v4/java/android/support/v4/widget/ScrollerCompat.java
index afbf897..f1b89da 100644
--- a/v4/java/android/support/v4/widget/ScrollerCompat.java
+++ b/v4/java/android/support/v4/widget/ScrollerCompat.java
@@ -54,6 +54,8 @@
boolean isOverScrolled(Object scroller);
int getFinalX(Object scroller);
int getFinalY(Object scroller);
+ boolean springBack(Object scroller, int startX, int startY, int minX, int maxX,
+ int minY, int maxY);
}
static final int CHASE_FRAME_TIME = 16; // ms per target frame
@@ -145,6 +147,12 @@
public int getFinalY(Object scroller) {
return ((Scroller) scroller).getFinalY();
}
+
+ @Override
+ public boolean springBack(Object scroller, int startX, int startY, int minX, int maxX,
+ int minY, int maxY) {
+ return false;
+ }
}
static class ScrollerCompatImplGingerbread implements ScrollerCompatImpl {
@@ -233,6 +241,13 @@
public int getFinalY(Object scroller) {
return ScrollerCompatGingerbread.getFinalY(scroller);
}
+
+ @Override
+ public boolean springBack(Object scroller, int startX, int startY, int minX, int maxX,
+ int minY, int maxY) {
+ return ScrollerCompatGingerbread.springBack(scroller, startX, startY, minX, maxX,
+ minY, maxY);
+ }
}
static class ScrollerCompatImplIcs extends ScrollerCompatImplGingerbread {
@@ -423,6 +438,22 @@
}
/**
+ * Call this when you want to 'spring back' into a valid coordinate range.
+ *
+ * @param startX Starting X coordinate
+ * @param startY Starting Y coordinate
+ * @param minX Minimum valid X value
+ * @param maxX Maximum valid X value
+ * @param minY Minimum valid Y value
+ * @param maxY Minimum valid Y value
+ * @return true if a springback was initiated, false if startX and startY were
+ * already within the valid range.
+ */
+ public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+ return mImpl.springBack(mScroller, startX, startY, minX, maxX, minY, maxY);
+ }
+
+ /**
* Stops the animation. Aborting the animation causes the scroller to move to the final x and y
* position.
*/
diff --git a/v4/jellybean-mr2/android/support/v4/media/TransportMediatorJellybeanMR2.java b/v4/jellybean-mr2/android/support/v4/media/TransportMediatorJellybeanMR2.java
index 38b6778..be3555e 100644
--- a/v4/jellybean-mr2/android/support/v4/media/TransportMediatorJellybeanMR2.java
+++ b/v4/jellybean-mr2/android/support/v4/media/TransportMediatorJellybeanMR2.java
@@ -28,9 +28,7 @@
import android.view.View;
import android.view.ViewTreeObserver;
-class TransportMediatorJellybeanMR2
- implements RemoteControlClient.OnGetPlaybackPositionListener,
- RemoteControlClient.OnPlaybackPositionUpdateListener {
+class TransportMediatorJellybeanMR2 {
final Context mContext;
final AudioManager mAudioManager;
final View mTargetView;
@@ -75,7 +73,20 @@
mTransportCallback.handleAudioFocusChange(focusChange);
}
};
-
+ final RemoteControlClient.OnGetPlaybackPositionListener mGetPlaybackPositionListener
+ = new RemoteControlClient.OnGetPlaybackPositionListener() {
+ @Override
+ public long onGetPlaybackPosition() {
+ return mTransportCallback.getPlaybackPosition();
+ }
+ };
+ final RemoteControlClient.OnPlaybackPositionUpdateListener mPlaybackPositionUpdateListener
+ = new RemoteControlClient.OnPlaybackPositionUpdateListener() {
+ public void onPlaybackPositionUpdate(long newPositionMs) {
+ mTransportCallback.playbackPositionUpdate(newPositionMs);
+ }
+ };
+
PendingIntent mPendingIntent;
RemoteControlClient mRemoteControl;
boolean mFocused;
@@ -112,8 +123,8 @@
mPendingIntent = PendingIntent.getBroadcast(mContext, 0, mIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
mRemoteControl = new RemoteControlClient(mPendingIntent);
- mRemoteControl.setOnGetPlaybackPositionListener(this);
- mRemoteControl.setPlaybackPositionUpdateListener(this);
+ mRemoteControl.setOnGetPlaybackPositionListener(mGetPlaybackPositionListener);
+ mRemoteControl.setPlaybackPositionUpdateListener(mPlaybackPositionUpdateListener);
}
void gainFocus() {
@@ -145,16 +156,6 @@
}
}
- @Override
- public long onGetPlaybackPosition() {
- return mTransportCallback.getPlaybackPosition();
- }
-
- @Override
- public void onPlaybackPositionUpdate(long newPositionMs) {
- mTransportCallback.playbackPositionUpdate(newPositionMs);
- }
-
public void refreshState(boolean playing, long position, int transportControls) {
if (mRemoteControl != null) {
mRemoteControl.setPlaybackState(playing ? RemoteControlClient.PLAYSTATE_PLAYING
diff --git a/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
index 1c5dca7..1f008f8 100644
--- a/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
+++ b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
@@ -21,7 +21,7 @@
import android.media.RemoteControlClient;
import android.os.SystemClock;
-public class MediaSessionCompatApi18 {
+class MediaSessionCompatApi18 {
/***** PlaybackState actions *****/
private static final long ACTION_SEEK_TO = 1 << 8;
diff --git a/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
index 61039d3..f3c19e2 100644
--- a/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
+++ b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
@@ -15,14 +15,13 @@
*/
package android.support.v4.media.session;
-import android.graphics.Bitmap;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.Rating;
import android.media.RemoteControlClient;
import android.os.Bundle;
-public class MediaSessionCompatApi19 {
+class MediaSessionCompatApi19 {
/***** PlaybackState actions *****/
private static final long ACTION_SET_RATING = 1 << 7;
diff --git a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
index 855cdba..56cb6fb 100644
--- a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
+++ b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
@@ -17,9 +17,9 @@
package android.support.v4.graphics;
import android.graphics.Color;
-import android.support.v4.graphics.ColorUtils;
import android.test.AndroidTestCase;
+import java.lang.Integer;
import java.util.ArrayList;
/**
@@ -31,21 +31,31 @@
private static final float ALLOWED_OFFSET_HUE = 360 * 0.005f;
private static final float ALLOWED_OFFSET_SATURATION = 0.005f;
private static final float ALLOWED_OFFSET_LIGHTNESS = 0.005f;
+ private static final float ALLOWED_OFFSET_MIN_ALPHA = 0.01f;
private static final int ALLOWED_OFFSET_RGB_COMPONENT = 2;
private static final ArrayList<TestEntry> sEntryList = new ArrayList<>();
static {
- sEntryList.add(new TestEntry(Color.BLACK).setHsl(0f, 0f, 0f));
- sEntryList.add(new TestEntry(Color.WHITE).setHsl(0f, 0f, 1f));
- sEntryList.add(new TestEntry(Color.BLUE).setHsl(240f, 1f, 0.5f));
- sEntryList.add(new TestEntry(Color.GREEN).setHsl(120f, 1f, 0.5f));
- sEntryList.add(new TestEntry(Color.RED).setHsl(0f, 1f, 0.5f));
- sEntryList.add(new TestEntry(Color.CYAN).setHsl(180f, 1f, 0.5f));
- sEntryList.add(new TestEntry(0x2196F3).setHsl(207f, 0.9f, 0.54f));
- sEntryList.add(new TestEntry(0xD1C4E9).setHsl(261f, 0.46f, 0.84f));
- sEntryList.add(new TestEntry(0x311B92).setHsl(251.09f, 0.687f, 0.339f));
+ sEntryList.add(new TestEntry(Color.BLACK).setHsl(0f, 0f, 0f)
+ .setWhiteMinAlpha30(0.35f).setWhiteMinAlpha45(0.46f));
+ sEntryList.add(new TestEntry(Color.WHITE).setHsl(0f, 0f, 1f)
+ .setBlackMinAlpha30(0.42f).setBlackMinAlpha45(0.54f));
+ sEntryList.add(new TestEntry(Color.BLUE).setHsl(240f, 1f, 0.5f)
+ .setWhiteMinAlpha30(0.55f).setWhiteMinAlpha45(0.71f));
+ sEntryList.add(new TestEntry(Color.GREEN).setHsl(120f, 1f, 0.5f)
+ .setBlackMinAlpha30(0.43f).setBlackMinAlpha45(0.55f));
+ sEntryList.add(new TestEntry(Color.RED).setHsl(0f, 1f, 0.5f)
+ .setWhiteMinAlpha30(0.84f).setBlackMinAlpha30(0.55f).setBlackMinAlpha45(0.78f));
+ sEntryList.add(new TestEntry(Color.CYAN).setHsl(180f, 1f, 0.5f)
+ .setBlackMinAlpha30(0.43f).setBlackMinAlpha45(0.55f));
+ sEntryList.add(new TestEntry(0xFF2196F3).setHsl(207f, 0.9f, 0.54f)
+ .setBlackMinAlpha30(0.52f).setWhiteMinAlpha30(0.97f).setBlackMinAlpha45(0.7f));
+ sEntryList.add(new TestEntry(0xFFD1C4E9).setHsl(261f, 0.46f, 0.84f)
+ .setBlackMinAlpha30(0.45f).setBlackMinAlpha45(0.58f));
+ sEntryList.add(new TestEntry(0xFF311B92).setHsl(251.09f, 0.687f, 0.339f)
+ .setWhiteMinAlpha30(0.39f).setWhiteMinAlpha45(0.54f));
}
public void testToHSL() {
@@ -72,6 +82,28 @@
}
}
+ public void testMinAlphas() {
+ for (TestEntry entry : sEntryList) {
+ testMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
+ ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 3.0f));
+ testMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
+ ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 4.5f));
+ testMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
+ ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 3.0f));
+ testMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
+ ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 4.5f));
+ }
+ }
+
+ private static void testMinAlpha(String title, int color, float expected, int actual) {
+ final String message = title + " text within error for #" + Integer.toHexString(color);
+ if (expected < 0) {
+ assertEquals(message, actual, -1);
+ } else {
+ assertClose(message, expected, actual / 255f, ALLOWED_OFFSET_MIN_ALPHA);
+ }
+ }
+
private static void assertClose(String message, float expected, float actual,
float allowedOffset) {
StringBuilder sb = new StringBuilder(message);
@@ -114,6 +146,10 @@
private static class TestEntry {
final int rgb;
final float[] hsl = new float[3];
+ float blackMinAlpha45 = -1;
+ float blackMinAlpha30 = -1;
+ float whiteMinAlpha45 = -1;
+ float whiteMinAlpha30 = -1;
TestEntry(int rgb) {
this.rgb = rgb;
@@ -125,5 +161,25 @@
hsl[2] = l;
return this;
}
+
+ TestEntry setBlackMinAlpha30(float minAlpha) {
+ blackMinAlpha30 = minAlpha;
+ return this;
+ }
+
+ TestEntry setBlackMinAlpha45(float minAlpha) {
+ blackMinAlpha45 = minAlpha;
+ return this;
+ }
+
+ TestEntry setWhiteMinAlpha30(float minAlpha) {
+ whiteMinAlpha30 = minAlpha;
+ return this;
+ }
+
+ TestEntry setWhiteMinAlpha45(float minAlpha) {
+ whiteMinAlpha45 = minAlpha;
+ return this;
+ }
}
}
\ No newline at end of file
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index 336e3a7..fcb8a82 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -368,6 +368,7 @@
field public static int alertDialogCenterButtons;
field public static int alertDialogStyle;
field public static int alertDialogTheme;
+ field public static int allowStacking;
field public static int arrowHeadLength;
field public static int arrowShaftLength;
field public static int autoCompleteTextViewStyle;
@@ -477,6 +478,7 @@
field public static int searchHintIcon;
field public static int searchIcon;
field public static int searchViewStyle;
+ field public static int seekBarStyle;
field public static int selectableItemBackground;
field public static int selectableItemBackgroundBorderless;
field public static int showAsAction;
@@ -540,6 +542,7 @@
field public static int abc_action_bar_embed_tabs;
field public static int abc_action_bar_embed_tabs_pre_jb;
field public static int abc_action_bar_expanded_action_views_exclusive;
+ field public static int abc_allow_stacked_button_bar;
field public static int abc_config_actionMenuItemAllCaps;
field public static int abc_config_allowActionMenuItemTextWithIcon;
field public static int abc_config_closeDialogWhenTouchOutside;
@@ -664,6 +667,8 @@
field public static int abc_panel_menu_list_width;
field public static int abc_search_view_preferred_width;
field public static int abc_search_view_text_min_width;
+ field public static int abc_seekbar_track_background_height_material;
+ field public static int abc_seekbar_track_progress_height_material;
field public static int abc_switch_padding;
field public static int abc_text_size_body_1_material;
field public static int abc_text_size_body_2_material;
@@ -748,6 +753,13 @@
field public static int abc_menu_hardkey_panel_mtrl_mult;
field public static int abc_popup_background_mtrl_mult;
field public static int abc_ratingbar_full_material;
+ field public static int abc_scrubber_control_off_mtrl_alpha;
+ field public static int abc_scrubber_control_to_pressed_mtrl_000;
+ field public static int abc_scrubber_control_to_pressed_mtrl_005;
+ field public static int abc_scrubber_primary_mtrl_alpha;
+ field public static int abc_scrubber_track_mtrl_alpha;
+ field public static int abc_seekbar_thumb_material;
+ field public static int abc_seekbar_track_material;
field public static int abc_spinner_mtrl_am_alpha;
field public static int abc_spinner_textfield_background_material;
field public static int abc_switch_thumb_material;
@@ -837,6 +849,7 @@
field public static int showCustom;
field public static int showHome;
field public static int showTitle;
+ field public static int spacer;
field public static int split_action_bar;
field public static int src_atop;
field public static int src_in;
@@ -877,6 +890,7 @@
field public static int abc_action_mode_close_item_material;
field public static int abc_activity_chooser_view;
field public static int abc_activity_chooser_view_list_item;
+ field public static int abc_alert_dialog_button_bar_material;
field public static int abc_alert_dialog_material;
field public static int abc_dialog_title_material;
field public static int abc_expanded_menu_layout;
@@ -916,6 +930,8 @@
field public static int abc_action_mode_done;
field public static int abc_activity_chooser_view_see_all;
field public static int abc_activitychooserview_choose_application;
+ field public static int abc_capital_off;
+ field public static int abc_capital_on;
field public static int abc_search_hint;
field public static int abc_searchview_description_clear;
field public static int abc_searchview_description_query;
@@ -1067,6 +1083,7 @@
field public static int Base_Widget_AppCompat_RatingBar;
field public static int Base_Widget_AppCompat_SearchView;
field public static int Base_Widget_AppCompat_SearchView_ActionBar;
+ field public static int Base_Widget_AppCompat_SeekBar;
field public static int Base_Widget_AppCompat_Spinner;
field public static int Base_Widget_AppCompat_Spinner_Underlined;
field public static int Base_Widget_AppCompat_TextView_SpinnerItem;
@@ -1225,6 +1242,7 @@
field public static int Widget_AppCompat_RatingBar;
field public static int Widget_AppCompat_SearchView;
field public static int Widget_AppCompat_SearchView_ActionBar;
+ field public static int Widget_AppCompat_SeekBar;
field public static int Widget_AppCompat_Spinner;
field public static int Widget_AppCompat_Spinner_DropDown;
field public static int Widget_AppCompat_Spinner_DropDown_ActionBar;
@@ -1289,6 +1307,7 @@
field public static final int[] AppCompatTextView;
field public static int AppCompatTextView_android_textAppearance;
field public static int AppCompatTextView_textAllCaps;
+ field public static int ButtonBarLayout_allowStacking;
field public static final int[] CompoundButton;
field public static int CompoundButton_android_button;
field public static int CompoundButton_buttonTint;
@@ -1394,6 +1413,10 @@
field public static int SwitchCompat_thumbTextPadding;
field public static int SwitchCompat_track;
field public static final int[] TextAppearance;
+ field public static int TextAppearance_android_shadowColor;
+ field public static int TextAppearance_android_shadowDx;
+ field public static int TextAppearance_android_shadowDy;
+ field public static int TextAppearance_android_shadowRadius;
field public static int TextAppearance_android_textColor;
field public static int TextAppearance_android_textSize;
field public static int TextAppearance_android_textStyle;
@@ -1483,6 +1506,7 @@
field public static int Theme_radioButtonStyle;
field public static int Theme_ratingBarStyle;
field public static int Theme_searchViewStyle;
+ field public static int Theme_seekBarStyle;
field public static int Theme_selectableItemBackground;
field public static int Theme_selectableItemBackgroundBorderless;
field public static int Theme_spinnerDropDownItemStyle;
@@ -1565,6 +1589,7 @@
method public int getDirection();
method public float getGapSize();
method public int getOpacity();
+ method public final android.graphics.Paint getPaint();
method public float getProgress();
method public boolean isSpinEnabled();
method public void setAlpha(int);
@@ -1708,6 +1733,12 @@
ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet, int);
}
+ public class AppCompatSeekBar extends android.widget.SeekBar {
+ ctor public AppCompatSeekBar(android.content.Context);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet, int);
+ }
+
public class AppCompatSpinner extends android.widget.Spinner {
ctor public AppCompatSpinner(android.content.Context);
ctor public AppCompatSpinner(android.content.Context, int);
@@ -1817,6 +1848,7 @@
method public void setSoftInputMode(int);
method public void setVerticalOffset(int);
method public void setWidth(int);
+ method public void setWindowLayoutType(int);
method public void show();
field public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; // 0x0
field public static final int INPUT_METHOD_NEEDED = 1; // 0x1
diff --git a/v7/appcompat/res-public/values/public_styles.xml b/v7/appcompat/res-public/values/public_styles.xml
index 4b42d0f..083fbc6 100644
--- a/v7/appcompat/res-public/values/public_styles.xml
+++ b/v7/appcompat/res-public/values/public_styles.xml
@@ -135,6 +135,7 @@
<public type="style" name="Widget.AppCompat.RatingBar"/>
<public type="style" name="Widget.AppCompat.SearchView"/>
<public type="style" name="Widget.AppCompat.SearchView.ActionBar"/>
+ <public type="style" name="Widget.AppCompat.SeekBar"/>
<public type="style" name="Widget.AppCompat.Spinner"/>
<public type="style" name="Widget.AppCompat.Spinner.DropDown"/>
<public type="style" name="Widget.AppCompat.Spinner.DropDown.ActionBar"/>
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png
new file mode 100644
index 0000000..4efe298
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png
new file mode 100644
index 0000000..543dec3
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png
new file mode 100644
index 0000000..9930b3a
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png
new file mode 100644
index 0000000..4cfb1a7
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png
new file mode 100644
index 0000000..32ddf7a
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png
new file mode 100644
index 0000000..10df639
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png
new file mode 100644
index 0000000..f83b1ef
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png
new file mode 100644
index 0000000..e9efb20f
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png
new file mode 100644
index 0000000..a4ab0a1
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png
new file mode 100644
index 0000000..db9e172
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png
new file mode 100644
index 0000000..138f643
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
new file mode 100644
index 0000000..cd41d74
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
new file mode 100644
index 0000000..8d67525
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png
new file mode 100644
index 0000000..2b4734d
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png
new file mode 100644
index 0000000..805cb29
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png
new file mode 100644
index 0000000..5268745
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
new file mode 100644
index 0000000..adffc14
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
new file mode 100644
index 0000000..f3d16d5
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png
new file mode 100644
index 0000000..6a82af5
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png
new file mode 100644
index 0000000..c3791fc
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
new file mode 100644
index 0000000..e5a43bb
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
new file mode 100644
index 0000000..eeb37c1
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable/abc_seekbar_thumb_material.xml b/v7/appcompat/res/drawable/abc_seekbar_thumb_material.xml
new file mode 100644
index 0000000..7fea83bc
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_seekbar_thumb_material.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:constantSize="true">
+ <item android:state_enabled="false" android:state_pressed="true">
+ <bitmap android:src="@drawable/abc_scrubber_control_off_mtrl_alpha"
+ android:gravity="center"/>
+ </item>
+ <item android:state_enabled="false">
+ <bitmap android:src="@drawable/abc_scrubber_control_off_mtrl_alpha"
+ android:gravity="center"/>
+ </item>
+ <item android:state_pressed="true">
+ <bitmap android:src="@drawable/abc_scrubber_control_to_pressed_mtrl_005"
+ android:gravity="center"/>
+ </item>
+ <item>
+ <bitmap android:src="@drawable/abc_scrubber_control_to_pressed_mtrl_000"
+ android:gravity="center"/>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_seekbar_track_material.xml b/v7/appcompat/res/drawable/abc_seekbar_track_material.xml
new file mode 100644
index 0000000..e68ac03
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_seekbar_track_material.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background"
+ android:drawable="@drawable/abc_scrubber_track_mtrl_alpha"/>
+ <item android:id="@android:id/secondaryProgress">
+ <scale android:scaleWidth="100%">
+ <selector>
+ <item android:state_enabled="false">
+ <color android:color="@android:color/transparent"/>
+ </item>
+ <item android:drawable="@drawable/abc_scrubber_primary_mtrl_alpha"/>
+ </selector>
+ </scale>
+ </item>
+ <item android:id="@android:id/progress">
+ <scale android:scaleWidth="100%">
+ <selector>
+ <item android:state_enabled="false">
+ <color android:color="@android:color/transparent"/>
+ </item>
+ <item android:drawable="@drawable/abc_scrubber_primary_mtrl_alpha"/>
+ </selector>
+ </scale>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
new file mode 100644
index 0000000..4405707
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+
+<android.support.v7.internal.widget.ButtonBarLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:paddingLeft="12dp"
+ android:paddingRight="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:gravity="bottom"
+ app:allowStacking="@bool/abc_allow_stacked_button_bar"
+ style="?attr/buttonBarStyle">
+
+ <Button
+ android:id="@android:id/button3"
+ style="?attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <android.support.v4.widget.Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+
+ <Button
+ android:id="@android:id/button2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@android:id/button1"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</android.support.v7.internal.widget.ButtonBarLayout>
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
index 9ba81fd..01ff885 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
@@ -108,41 +108,6 @@
android:layout_height="wrap_content"/>
</FrameLayout>
- <LinearLayout
- android:id="@+id/buttonPanel"
- style="?attr/buttonBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layoutDirection="locale"
- android:orientation="horizontal"
- android:paddingLeft="12dp"
- android:paddingRight="12dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:gravity="bottom">
+ <include layout="@layout/abc_alert_dialog_button_bar_material" />
- <Button
- android:id="@android:id/button3"
- style="?attr/buttonBarNeutralButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <android.support.v4.widget.Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:visibility="invisible"/>
-
- <Button
- android:id="@android:id/button2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <Button
- android:id="@android:id/button1"
- style="?attr/buttonBarPositiveButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-az-rAZ/strings.xml b/v7/appcompat/res/values-az-rAZ/strings.xml
deleted file mode 100644
index a39f5f4..0000000
--- a/v7/appcompat/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2012 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="abc_action_mode_done" msgid="4076576682505996667">"Hazırdır"</string>
- <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Evə get"</string>
- <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yuxarı get"</string>
- <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Daha çox seçim"</string>
- <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Dağıt"</string>
- <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
- <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
- <string name="abc_searchview_description_search" msgid="8264924765203268293">"Axtarış"</string>
- <string name="abc_search_hint" msgid="7723749260725869598">"Axtarış..."</string>
- <string name="abc_searchview_description_query" msgid="2550479030709304392">"Axtarış sorğusu"</string>
- <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Sorğunu təmizlə"</string>
- <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Sorğunu göndərin"</string>
- <string name="abc_searchview_description_voice" msgid="893419373245838918">"Səsli axtarış"</string>
- <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Tətbiq seçin"</string>
- <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Hamısına baxın"</string>
- <!-- String.format failed for translation -->
- <!-- no translation found for abc_shareactionprovider_share_with_application (7165123711973476752) -->
- <skip />
- <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Bununla paylaşın"</string>
- <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string>
-</resources>
diff --git a/v7/mediarouter/res/values-sw600dp/dimens.xml b/v7/appcompat/res/values-h320dp/bools.xml
similarity index 82%
rename from v7/mediarouter/res/values-sw600dp/dimens.xml
rename to v7/appcompat/res/values-h320dp/bools.xml
index 5b29058..5576c18 100644
--- a/v7/mediarouter/res/values-sw600dp/dimens.xml
+++ b/v7/appcompat/res/values-h320dp/bools.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- 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.
@@ -15,5 +15,5 @@
-->
<resources>
- <dimen name="mr_media_route_controller_art_max_height">480dp</dimen>
-</resources>
\ No newline at end of file
+ <bool name="abc_allow_stacked_button_bar">true</bool>
+</resources>
diff --git a/v7/appcompat/res/values-kk-rKZ/strings.xml b/v7/appcompat/res/values-kk-rKZ/strings.xml
index 2ce080f..52b9af5 100644
--- a/v7/appcompat/res/values-kk-rKZ/strings.xml
+++ b/v7/appcompat/res/values-kk-rKZ/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="abc_action_mode_done" msgid="4076576682505996667">"Дайын"</string>
+ <string name="abc_action_mode_done" msgid="4076576682505996667">"Орындалды"</string>
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Негізгі бетте қозғалу"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жоғары қозғалу"</string>
<string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Басқа опциялар"</string>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 47681f2..649f0ca 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -172,6 +172,8 @@
<style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.Material.RatingBar" />
+ <style name="Base.Widget.AppCompat.SeekBar" parent="android:Widget.Material.SeekBar" />
+
<style name="Base.Widget.AppCompat.Button" parent="android:Widget.Material.Button" />
<style name="Base.Widget.AppCompat.Button.Small" parent="android:Widget.Material.Button.Small" />
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 040bea1..c5c4244 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -371,6 +371,8 @@
<attr name="radioButtonStyle" format="reference" />
<!-- Default RatingBar style. -->
<attr name="ratingBarStyle" format="reference" />
+ <!-- Default SeekBar style. -->
+ <attr name="seekBarStyle" format="reference" />
<!-- Default Spinner style. -->
<attr name="spinnerStyle" format="reference" />
<!-- Default style for the Switch widget. -->
@@ -931,6 +933,10 @@
<attr name="android:textStyle" />
<attr name="android:typeface" />
<attr name="textAllCaps" />
+ <attr name="android:shadowColor"/>
+ <attr name="android:shadowDy"/>
+ <attr name="android:shadowDx"/>
+ <attr name="android:shadowRadius"/>
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -943,4 +949,11 @@
<attr name="listItemLayout" format="reference" />
</declare-styleable>
+ <!-- @hide -->
+ <declare-styleable name="ButtonBarLayout">
+ <!-- Whether to automatically stack the buttons when there is not
+ enough space to lay them out side-by-side. -->
+ <attr name="allowStacking" format="boolean" />
+ </declare-styleable>
+
</resources>
diff --git a/v7/appcompat/res/values/bools.xml b/v7/appcompat/res/values/bools.xml
index 79a5035..3508cf3 100644
--- a/v7/appcompat/res/values/bools.xml
+++ b/v7/appcompat/res/values/bools.xml
@@ -21,4 +21,8 @@
<bool name="abc_action_bar_expanded_action_views_exclusive">true</bool>
<bool name="abc_config_showMenuShortcutsWhenKeyboardPresent">false</bool>
+
+ <!-- Whether to allow vertically stacked button bars. This is disabled for
+ configurations with a small (e.g. less than 320dp) screen height. -->
+ <bool name="abc_allow_stacked_button_bar">false</bool>
</resources>
diff --git a/v7/appcompat/res/values/colors_material.xml b/v7/appcompat/res/values/colors_material.xml
index cf30f0dd..70fd21d 100644
--- a/v7/appcompat/res/values/colors_material.xml
+++ b/v7/appcompat/res/values/colors_material.xml
@@ -30,8 +30,8 @@
<color name="primary_dark_material_dark">@android:color/black</color>
<color name="primary_dark_material_light">@color/material_grey_600</color>
- <!-- 26% white (foreground) -->
- <color name="ripple_material_dark">#42ffffff</color>
+ <!-- 20% white (foreground) -->
+ <color name="ripple_material_dark">#33ffffff</color>
<!-- 12% black (foreground) -->
<color name="ripple_material_light">#1f000000</color>
diff --git a/v7/appcompat/res/values/dimens_material.xml b/v7/appcompat/res/values/dimens_material.xml
index f67127c..357dc3e 100644
--- a/v7/appcompat/res/values/dimens_material.xml
+++ b/v7/appcompat/res/values/dimens_material.xml
@@ -68,4 +68,7 @@
<item name="abc_disabled_alpha_material_light" format="float" type="dimen">0.26</item>
<item name="abc_disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
+ <dimen name="abc_seekbar_track_background_height_material">2dp</dimen>
+ <dimen name="abc_seekbar_track_progress_height_material">2dp</dimen>
+
</resources>
diff --git a/v7/appcompat/res/values/strings.xml b/v7/appcompat/res/values/strings.xml
index a04b396..6e2a622 100644
--- a/v7/appcompat/res/values/strings.xml
+++ b/v7/appcompat/res/values/strings.xml
@@ -68,4 +68,9 @@
for most appropriate textual indicator of "more than X".
[CHAR LIMIT=4] -->
<string name="status_bar_notification_info_overflow">999+</string>
+
+ <!-- Default text for a button that can be toggled on and off. -->
+ <string name="abc_capital_on">ON</string>
+ <!-- Default text for a button that can be toggled on and off. -->
+ <string name="abc_capital_off">OFF</string>
</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index e10c01f..9453944 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -224,6 +224,8 @@
<style name="AlertDialog.AppCompat.Light" parent="Base.AlertDialog.AppCompat.Light" />
+ <style name="Widget.AppCompat.SeekBar" parent="Base.Widget.AppCompat.SeekBar" />
+
<!-- Toolbar -->
<style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index d65b16f..0c3c056 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -386,6 +386,8 @@
<item name="android:background">?attr/controlBackground</item>
<item name="showText">false</item>
<item name="switchPadding">@dimen/abc_switch_padding</item>
+ <item name="android:textOn">@string/abc_capital_on</item>
+ <item name="android:textOff">@string/abc_capital_off</item>
</style>
<style name="Base.TextAppearance.AppCompat.Widget.Switch" parent="TextAppearance.AppCompat.Button" />
@@ -395,6 +397,16 @@
<item name="android:indeterminateDrawable">@drawable/abc_ratingbar_full_material</item>
</style>
+ <style name="Base.Widget.AppCompat.SeekBar" parent="android:Widget">
+ <item name="android:indeterminateOnly">false</item>
+ <item name="android:progressDrawable">@drawable/abc_seekbar_track_material</item>
+ <item name="android:indeterminateDrawable">@drawable/abc_seekbar_track_material</item>
+ <item name="android:thumb">@drawable/abc_seekbar_thumb_material</item>
+ <item name="android:focusable">true</item>
+ <item name="android:paddingLeft">16dip</item>
+ <item name="android:paddingRight">16dip</item>
+ </style>
+
<!-- Bordered ink button -->
<style name="Base.Widget.AppCompat.Button" parent="android:Widget">
<item name="android:background">@drawable/abc_btn_default_mtrl_shape</item>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index d0dfca5..298196f 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -229,6 +229,7 @@
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
<item name="ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+ <item name="seekBarStyle">@style/Widget.AppCompat.SeekBar</item>
<!-- Button styles -->
<item name="buttonStyle">@style/Widget.AppCompat.Button</item>
@@ -379,6 +380,7 @@
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
<item name="ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+ <item name="seekBarStyle">@style/Widget.AppCompat.SeekBar</item>
<!-- Button styles -->
<item name="buttonStyle">@style/Widget.AppCompat.Button</item>
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
index b8182e6..cdb048f 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
@@ -54,6 +54,8 @@
boolean mIsFloating;
// true if this activity has no title
boolean mWindowNoTitle;
+ // true if the theme has been read
+ boolean mThemeRead;
private CharSequence mTitle;
@@ -276,8 +278,9 @@
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
- return super.onMenuOpened(featureId, menu)
- || AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu);
+ super.onMenuOpened(featureId, menu);
+ AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu);
+ return true;
}
@Override
diff --git a/v7/appcompat/src/android/support/v7/graphics/drawable/DrawerArrowDrawable.java b/v7/appcompat/src/android/support/v7/graphics/drawable/DrawerArrowDrawable.java
index 82e8f18..2c658b1 100644
--- a/v7/appcompat/src/android/support/v7/graphics/drawable/DrawerArrowDrawable.java
+++ b/v7/appcompat/src/android/support/v7/graphics/drawable/DrawerArrowDrawable.java
@@ -447,6 +447,13 @@
}
/**
+ * Returns the paint instance used for all drawing.
+ */
+ public final Paint getPaint() {
+ return mPaint;
+ }
+
+ /**
* Linear interpolate between a and b with parameter t.
*/
private static float lerp(float a, float b, float t) {
diff --git a/v7/appcompat/src/android/support/v7/internal/app/AppCompatViewInflater.java b/v7/appcompat/src/android/support/v7/internal/app/AppCompatViewInflater.java
index 621fc51..7cf3e2d 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/AppCompatViewInflater.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/AppCompatViewInflater.java
@@ -17,9 +17,13 @@
package android.support.v7.internal.app;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.res.TypedArray;
+import android.os.Build;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
+import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.view.ContextThemeWrapper;
import android.support.v7.widget.AppCompatAutoCompleteTextView;
@@ -30,8 +34,8 @@
import android.support.v7.widget.AppCompatMultiAutoCompleteTextView;
import android.support.v7.widget.AppCompatRadioButton;
import android.support.v7.widget.AppCompatRatingBar;
+import android.support.v7.widget.AppCompatSeekBar;
import android.support.v7.widget.AppCompatSpinner;
-import android.support.v7.internal.widget.ViewUtils;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.util.Log;
@@ -39,7 +43,8 @@
import android.view.View;
import java.lang.reflect.Constructor;
-import java.util.HashMap;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Map;
/**
@@ -54,8 +59,9 @@
*/
public class AppCompatViewInflater {
- static final Class<?>[] sConstructorSignature = new Class[]{
+ private static final Class<?>[] sConstructorSignature = new Class[]{
Context.class, AttributeSet.class};
+ private static final int[] sOnClickAttrs = new int[]{android.R.attr.onClick};
private static final String LOG_TAG = "AppCompatViewInflater";
@@ -79,37 +85,57 @@
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
+ View view = null;
+
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "EditText":
- return new AppCompatEditText(context, attrs);
+ view = new AppCompatEditText(context, attrs);
+ break;
case "Spinner":
- return new AppCompatSpinner(context, attrs);
+ view = new AppCompatSpinner(context, attrs);
+ break;
case "CheckBox":
- return new AppCompatCheckBox(context, attrs);
+ view = new AppCompatCheckBox(context, attrs);
+ break;
case "RadioButton":
- return new AppCompatRadioButton(context, attrs);
+ view = new AppCompatRadioButton(context, attrs);
+ break;
case "CheckedTextView":
- return new AppCompatCheckedTextView(context, attrs);
+ view = new AppCompatCheckedTextView(context, attrs);
+ break;
case "AutoCompleteTextView":
- return new AppCompatAutoCompleteTextView(context, attrs);
+ view = new AppCompatAutoCompleteTextView(context, attrs);
+ break;
case "MultiAutoCompleteTextView":
- return new AppCompatMultiAutoCompleteTextView(context, attrs);
+ view = new AppCompatMultiAutoCompleteTextView(context, attrs);
+ break;
case "RatingBar":
- return new AppCompatRatingBar(context, attrs);
+ view = new AppCompatRatingBar(context, attrs);
+ break;
case "Button":
- return new AppCompatButton(context, attrs);
+ view = new AppCompatButton(context, attrs);
+ break;
case "TextView":
- return new AppCompatTextView(context, attrs);
+ view = new AppCompatTextView(context, attrs);
+ break;
+ case "SeekBar":
+ view = new AppCompatSeekBar(context, attrs);
+ break;
}
- if (originalContext != context) {
+ if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
- return createViewFromTag(context, name, attrs);
+ view = createViewFromTag(context, name, attrs);
}
- return null;
+ if (view != null) {
+ // If we have created a view, check it's android:onClick
+ checkOnClickListener(view, attrs);
+ }
+
+ return view;
}
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
@@ -138,6 +164,28 @@
}
}
+ /**
+ * android:onClick doesn't handle views with a ContextWrapper context. This method
+ * backports new framework functionality to traverse the Context wrappers to find a
+ * suitable target.
+ */
+ private void checkOnClickListener(View view, AttributeSet attrs) {
+ final Context context = view.getContext();
+
+ if (!ViewCompat.hasOnClickListeners(view) || !(context instanceof ContextWrapper)) {
+ // Skip our compat functionality if: the view doesn't have an onClickListener,
+ // or the Context isn't a ContextWrapper
+ return;
+ }
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, sOnClickAttrs);
+ final String handlerName = a.getString(0);
+ if (handlerName != null) {
+ view.setOnClickListener(new DeclaredOnClickListener(view, handlerName));
+ }
+ a.recycle();
+ }
+
private View createView(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
@@ -190,4 +238,70 @@
}
return context;
}
+
+ /**
+ * An implementation of OnClickListener that attempts to lazily load a
+ * named click handling method from a parent or ancestor context.
+ */
+ private static class DeclaredOnClickListener implements View.OnClickListener {
+ private final View mHostView;
+ private final String mMethodName;
+
+ private Method mResolvedMethod;
+ private Context mResolvedContext;
+
+ public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
+ mHostView = hostView;
+ mMethodName = methodName;
+ }
+
+ @Override
+ public void onClick(@NonNull View v) {
+ if (mResolvedMethod == null) {
+ resolveMethod(mHostView.getContext(), mMethodName);
+ }
+
+ try {
+ mResolvedMethod.invoke(mResolvedContext, v);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "Could not execute non-public method for android:onClick", e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(
+ "Could not execute method for android:onClick", e);
+ }
+ }
+
+ @NonNull
+ private void resolveMethod(@Nullable Context context, @NonNull String name) {
+ while (context != null) {
+ try {
+ if (!context.isRestricted()) {
+ final Method method = context.getClass().getMethod(mMethodName, View.class);
+ if (method != null) {
+ mResolvedMethod = method;
+ mResolvedContext = context;
+ return;
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ // Failed to find method, keep searching up the hierarchy.
+ }
+
+ if (context instanceof ContextWrapper) {
+ context = ((ContextWrapper) context).getBaseContext();
+ } else {
+ // Can't search up the hierarchy, null out and fail.
+ context = null;
+ }
+ }
+
+ final int id = mHostView.getId();
+ final String idText = id == View.NO_ID ? "" : " with id '"
+ + mHostView.getContext().getResources().getResourceEntryName(id) + "'";
+ throw new IllegalStateException("Could not find method " + mMethodName
+ + "(View) in a parent or ancestor Context for android:onClick "
+ + "attribute defined on view " + mHostView.getClass() + idText);
+ }
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/internal/widget/ButtonBarLayout.java
new file mode 100644
index 0000000..dd0044b
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ButtonBarLayout.java
@@ -0,0 +1,110 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * An extension of LinearLayout that automatically switches to vertical
+ * orientation when it can't fit its child views horizontally.
+ *
+ * @hide
+ */
+public class ButtonBarLayout extends LinearLayout {
+
+ /** Whether the current configuration allows stacking. */
+ private boolean mAllowStacking;
+ private int mLastWidthSize = -1;
+
+ public ButtonBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+ ta.recycle();
+ }
+
+ public void setAllowStacking(boolean allowStacking) {
+ if (mAllowStacking != allowStacking) {
+ mAllowStacking = allowStacking;
+ if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
+ setStacked(false);
+ }
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ if (mAllowStacking) {
+ if (widthSize > mLastWidthSize && isStacked()) {
+ // We're being measured wider this time, try un-stacking.
+ setStacked(false);
+ }
+ mLastWidthSize = widthSize;
+ }
+ boolean needsRemeasure = false;
+ // If we're not stacked, make sure the measure spec is AT_MOST rather
+ // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
+ // know to stack the buttons.
+ final int initialWidthMeasureSpec;
+ if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+ initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+ // We'll need to remeasure again to fill excess space.
+ needsRemeasure = true;
+ } else {
+ initialWidthMeasureSpec = widthMeasureSpec;
+ }
+ super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+ if (mAllowStacking && !isStacked()) {
+ final int measuredWidth = getMeasuredWidthAndState();
+ final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
+ if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+ setStacked(true);
+ // Measure again in the new orientation.
+ needsRemeasure = true;
+ }
+ }
+ if (needsRemeasure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void setStacked(boolean stacked) {
+ setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+ setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
+ final View spacer = findViewById(R.id.spacer);
+ if (spacer != null) {
+ spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
+ }
+ // Reverse the child order. This is specific to the Material button
+ // bar's layout XML and will probably not generalize.
+ final int childCount = getChildCount();
+ for (int i = childCount - 2; i >= 0; i--) {
+ bringChildToFront(getChildAt(i));
+ }
+ }
+
+ private boolean isStacked() {
+ return getOrientation() == LinearLayout.VERTICAL;
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
index e2e6c4c..15e0e9b 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
@@ -23,6 +23,7 @@
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.graphics.drawable.DrawableWrapper;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
@@ -50,6 +51,8 @@
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
+ protected int mMotionPosition;
+
private Field mIsChildViewEnabled;
private GateKeeperDrawable mSelector;
@@ -107,6 +110,16 @@
super.dispatchDraw(canvas);
}
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mMotionPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return super.onTouchEvent(ev);
+ }
+
protected void updateSelectorStateCompat() {
Drawable selector = getSelector();
if (selector != null && shouldShowSelectorCompat()) {
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
index d926e17..ec85157c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
@@ -191,10 +191,18 @@
DrawableCompat.setTintMode(drawable, tintMode);
}
} else if (resId == R.drawable.abc_cab_background_top_material) {
- return new LayerDrawable(new Drawable[] {
+ return new LayerDrawable(new Drawable[]{
getDrawable(R.drawable.abc_cab_background_internal_bg),
getDrawable(R.drawable.abc_cab_background_top_mtrl_alpha)
});
+ } else if (resId == R.drawable.abc_seekbar_track_material) {
+ LayerDrawable ld = (LayerDrawable) drawable;
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
+ getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
+ getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
+ getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
} else {
final boolean usedColorFilter = tintDrawableUsingColorFilter(resId, drawable);
if (!usedColorFilter && failIfNotKnown) {
@@ -307,6 +315,8 @@
tint = getDefaultColorStateList(context);
} else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {
tint = createCheckableButtonColorStateList(context);
+ } else if (resId == R.drawable.abc_seekbar_thumb_material) {
+ tint = createSeekbarThumbColorStateList(context);
}
if (tint != null) {
@@ -541,6 +551,23 @@
return new ColorStateList(states, colors);
}
+ private ColorStateList createSeekbarThumbColorStateList(Context context) {
+ final int[][] states = new int[2][];
+ final int[] colors = new int[2];
+ int i = 0;
+
+ // Disabled state
+ states[i] = ThemeUtils.DISABLED_STATE_SET;
+ colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlActivated);
+ i++;
+
+ states[i] = ThemeUtils.EMPTY_STATE_SET;
+ colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
+ i++;
+
+ return new ColorStateList(states, colors);
+ }
+
private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
public ColorFilterLruCache(int maxSize) {
@@ -602,4 +629,8 @@
return filter;
}
+
+ private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {
+ d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java
new file mode 100644
index 0000000..7fd8f05
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java
@@ -0,0 +1,151 @@
+/*
+ * 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.widget;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Shader;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.support.v4.graphics.drawable.DrawableWrapper;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.TintManager;
+import android.support.v7.internal.widget.TintTypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.CompoundButton;
+import android.widget.ProgressBar;
+
+class AppCompatProgressBarHelper {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.indeterminateDrawable,
+ android.R.attr.progressDrawable
+ };
+
+ private final ProgressBar mView;
+ final TintManager mTintManager;
+
+ private Bitmap mSampleTile;
+
+ AppCompatProgressBarHelper(ProgressBar view, TintManager tintManager) {
+ mView = view;
+ mTintManager = tintManager;
+ }
+
+ void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
+ TINT_ATTRS, defStyleAttr, 0);
+
+ Drawable drawable = a.getDrawableIfKnown(0);
+ if (drawable != null) {
+ mView.setIndeterminateDrawable(tileifyIndeterminate(drawable));
+ }
+
+ drawable = a.getDrawableIfKnown(1);
+ if (drawable != null) {
+ mView.setProgressDrawable(tileify(drawable, false));
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Converts a drawable to a tiled version of itself. It will recursively
+ * traverse layer and state list drawables.
+ */
+ private Drawable tileify(Drawable drawable, boolean clip) {
+ if (drawable instanceof DrawableWrapper) {
+ Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
+ if (inner != null) {
+ inner = tileify(inner, clip);
+ ((DrawableWrapper) drawable).setWrappedDrawable(inner);
+ }
+ } else if (drawable instanceof LayerDrawable) {
+ LayerDrawable background = (LayerDrawable) drawable;
+ final int N = background.getNumberOfLayers();
+ Drawable[] outDrawables = new Drawable[N];
+
+ for (int i = 0; i < N; i++) {
+ int id = background.getId(i);
+ outDrawables[i] = tileify(background.getDrawable(i),
+ (id == android.R.id.progress || id == android.R.id.secondaryProgress));
+ }
+ LayerDrawable newBg = new LayerDrawable(outDrawables);
+
+ for (int i = 0; i < N; i++) {
+ newBg.setId(i, background.getId(i));
+ }
+
+ return newBg;
+
+ } else if (drawable instanceof BitmapDrawable) {
+ final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+ if (mSampleTile == null) {
+ mSampleTile = tileBitmap;
+ }
+
+ final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+ final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
+ Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+ shapeDrawable.getPaint().setShader(bitmapShader);
+ return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
+ ClipDrawable.HORIZONTAL) : shapeDrawable;
+ }
+
+ return drawable;
+ }
+
+ /**
+ * Convert a AnimationDrawable for use as a barberpole animation.
+ * Each frame of the animation is wrapped in a ClipDrawable and
+ * given a tiling BitmapShader.
+ */
+ private Drawable tileifyIndeterminate(Drawable drawable) {
+ if (drawable instanceof AnimationDrawable) {
+ AnimationDrawable background = (AnimationDrawable) drawable;
+ final int N = background.getNumberOfFrames();
+ AnimationDrawable newBg = new AnimationDrawable();
+ newBg.setOneShot(background.isOneShot());
+
+ for (int i = 0; i < N; i++) {
+ Drawable frame = tileify(background.getFrame(i), true);
+ frame.setLevel(10000);
+ newBg.addFrame(frame, background.getDuration(i));
+ }
+ newBg.setLevel(10000);
+ drawable = newBg;
+ }
+ return drawable;
+ }
+
+ private Shape getDrawableShape() {
+ final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
+ return new RoundRectShape(roundedCorners, null, null);
+ }
+
+ Bitmap getSampleTime() {
+ return mSampleTile;
+ }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatRatingBar.java b/v7/appcompat/src/android/support/v7/widget/AppCompatRatingBar.java
index 51811a8..0bc14ab 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatRatingBar.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatRatingBar.java
@@ -18,23 +18,10 @@
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Shader;
-import android.graphics.drawable.AnimationDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ClipDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.graphics.drawable.shapes.Shape;
-import android.support.v4.graphics.drawable.DrawableWrapper;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.internal.widget.TintManager;
-import android.support.v7.internal.widget.TintTypedArray;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.widget.RatingBar;
/**
@@ -45,12 +32,8 @@
*/
public class AppCompatRatingBar extends RatingBar {
- private static final int[] TINT_ATTRS = {
- android.R.attr.indeterminateDrawable,
- android.R.attr.progressDrawable
- };
-
- private Bitmap mSampleTile;
+ private AppCompatProgressBarHelper mAppCompatProgressBarHelper;
+ private TintManager mTintManager;
public AppCompatRatingBar(Context context) {
this(context, null);
@@ -63,104 +46,19 @@
public AppCompatRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- if (TintManager.SHOULD_BE_USED) {
- TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
- TINT_ATTRS, defStyleAttr, 0);
+ mTintManager = TintManager.get(context);
- Drawable drawable = a.getDrawableIfKnown(0);
- if (drawable != null) {
- setIndeterminateDrawable(tileifyIndeterminate(drawable));
- }
-
- drawable = a.getDrawableIfKnown(1);
- if (drawable != null) {
- setProgressDrawable(tileify(drawable, false));
- }
-
- a.recycle();
- }
- }
-
- /**
- * Converts a drawable to a tiled version of itself. It will recursively
- * traverse layer and state list drawables.
- */
- private Drawable tileify(Drawable drawable, boolean clip) {
- if (drawable instanceof DrawableWrapper) {
- Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
- if (inner != null) {
- inner = tileify(inner, clip);
- ((DrawableWrapper) drawable).setWrappedDrawable(inner);
- }
- } else if (drawable instanceof LayerDrawable) {
- LayerDrawable background = (LayerDrawable) drawable;
- final int N = background.getNumberOfLayers();
- Drawable[] outDrawables = new Drawable[N];
-
- for (int i = 0; i < N; i++) {
- int id = background.getId(i);
- outDrawables[i] = tileify(background.getDrawable(i),
- (id == android.R.id.progress || id == android.R.id.secondaryProgress));
- }
- LayerDrawable newBg = new LayerDrawable(outDrawables);
-
- for (int i = 0; i < N; i++) {
- newBg.setId(i, background.getId(i));
- }
-
- return newBg;
-
- } else if (drawable instanceof BitmapDrawable) {
- final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
- if (mSampleTile == null) {
- mSampleTile = tileBitmap;
- }
-
- final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
- final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
- Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
- shapeDrawable.getPaint().setShader(bitmapShader);
- return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
- ClipDrawable.HORIZONTAL) : shapeDrawable;
- }
-
- return drawable;
- }
-
- /**
- * Convert a AnimationDrawable for use as a barberpole animation.
- * Each frame of the animation is wrapped in a ClipDrawable and
- * given a tiling BitmapShader.
- */
- private Drawable tileifyIndeterminate(Drawable drawable) {
- if (drawable instanceof AnimationDrawable) {
- AnimationDrawable background = (AnimationDrawable) drawable;
- final int N = background.getNumberOfFrames();
- AnimationDrawable newBg = new AnimationDrawable();
- newBg.setOneShot(background.isOneShot());
-
- for (int i = 0; i < N; i++) {
- Drawable frame = tileify(background.getFrame(i), true);
- frame.setLevel(10000);
- newBg.addFrame(frame, background.getDuration(i));
- }
- newBg.setLevel(10000);
- drawable = newBg;
- }
- return drawable;
- }
-
- private Shape getDrawableShape() {
- final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
- return new RoundRectShape(roundedCorners, null, null);
+ mAppCompatProgressBarHelper = new AppCompatProgressBarHelper(this, mTintManager);
+ mAppCompatProgressBarHelper.loadFromAttributes(attrs, defStyleAttr);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSampleTile != null) {
- final int width = mSampleTile.getWidth() * getNumStars();
+ Bitmap sampleTile = mAppCompatProgressBarHelper.getSampleTime();
+ if (sampleTile != null) {
+ final int width = sampleTile.getWidth() * getNumStars();
setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0),
getMeasuredHeight());
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
new file mode 100644
index 0000000..86ec881
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.TintManager;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+/**
+ * A {@link SeekBar} which supports compatible features on older version of the platform.
+ *
+ * <p>This will automatically be used when you use {@link SeekBar} in your layouts.
+ * You should only need to manually use this class when writing custom views.</p>
+ */
+public class AppCompatSeekBar extends SeekBar {
+
+ private AppCompatSeekBarHelper mAppCompatSeekBarHelper;
+ private TintManager mTintManager;
+
+ public AppCompatSeekBar(Context context) {
+ this(context, null);
+ }
+
+ public AppCompatSeekBar(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.seekBarStyle);
+ }
+
+ public AppCompatSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ mTintManager = TintManager.get(context);
+
+ mAppCompatSeekBarHelper = new AppCompatSeekBarHelper(this, mTintManager);
+ mAppCompatSeekBarHelper.loadFromAttributes(attrs, defStyleAttr);
+ }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
new file mode 100644
index 0000000..3211942
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.widget;
+
+import android.graphics.drawable.Drawable;
+import android.support.v7.internal.widget.TintManager;
+import android.support.v7.internal.widget.TintTypedArray;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+class AppCompatSeekBarHelper extends AppCompatProgressBarHelper {
+
+ private static final int[] TINT_ATTRS = {
+ android.R.attr.thumb
+ };
+
+ private final SeekBar mView;
+
+ AppCompatSeekBarHelper(SeekBar view, TintManager tintManager) {
+ super(view, tintManager);
+ mView = view;
+ }
+
+ void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ super.loadFromAttributes(attrs, defStyleAttr);
+
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
+ TINT_ATTRS, defStyleAttr, 0);
+ Drawable drawable = a.getDrawableIfKnown(0);
+ if (drawable != null) {
+ mView.setThumb(drawable);
+ }
+ a.recycle();
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index 0d2bcb8..2b03774 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -26,6 +26,7 @@
import android.os.SystemClock;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.widget.ListViewAutoScrollHelper;
import android.support.v4.widget.PopupWindowCompat;
@@ -43,6 +44,7 @@
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.LinearLayout;
@@ -93,6 +95,7 @@
private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownHorizontalOffset;
private int mDropDownVerticalOffset;
+ private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
private boolean mDropDownVerticalOffsetSet;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -119,7 +122,7 @@
private final ListSelectorHider mHideSelector = new ListSelectorHider();
private Runnable mShowDropDownRunnable;
- private Handler mHandler = new Handler();
+ private final Handler mHandler;
private Rect mTempRect = new Rect();
@@ -226,6 +229,7 @@
*/
public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
+ mHandler = new Handler(context.getMainLooper());
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
defStyleAttr, defStyleRes);
@@ -531,6 +535,19 @@
}
/**
+ * Set the layout type for this popup window.
+ * <p>
+ * See {@link WindowManager.LayoutParams#type} for possible values.
+ *
+ * @param layoutType Layout type for this window.
+ *
+ * @see WindowManager.LayoutParams#type
+ */
+ public void setWindowLayoutType(int layoutType) {
+ mDropDownWindowLayoutType = layoutType;
+ }
+
+ /**
* Sets a listener to receive events when a list item is clicked.
*
* @param clickListener Listener to register
@@ -583,12 +600,11 @@
public void show() {
int height = buildDropDown();
- int widthSpec = 0;
- int heightSpec = 0;
-
boolean noInputMethod = isInputMethodNotNeeded();
+ PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
if (mPopup.isShowing()) {
+ final int widthSpec;
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
@@ -599,19 +615,19 @@
widthSpec = mDropDownWidth;
}
+ final int heightSpec;
if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
if (noInputMethod) {
- mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
- ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
+ mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0);
+ mPopup.setHeight(0);
} else {
- mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
- ViewGroup.LayoutParams.MATCH_PARENT : 0,
- ViewGroup.LayoutParams.MATCH_PARENT);
+ mPopup.setWidth(mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0);
+ mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
}
} else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
heightSpec = height;
@@ -622,29 +638,33 @@
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
- mDropDownVerticalOffset, widthSpec, heightSpec);
+ mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
+ (heightSpec < 0)? -1 : heightSpec);
} else {
+ final int widthSpec;
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
- mPopup.setWidth(getAnchorView().getWidth());
+ widthSpec = getAnchorView().getWidth();
} else {
- mPopup.setWidth(mDropDownWidth);
+ widthSpec = mDropDownWidth;
}
}
+ final int heightSpec;
if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
- mPopup.setHeight(height);
+ heightSpec = height;
} else {
- mPopup.setHeight(mDropDownHeight);
+ heightSpec = mDropDownHeight;
}
}
- mPopup.setWindowLayoutMode(widthSpec, heightSpec);
+ mPopup.setWidth(widthSpec);
+ mPopup.setHeight(heightSpec);
setPopupClipToScreenEnabled(true);
// use outside touchable to dismiss drop down when touching outside of it, so
@@ -1126,10 +1146,19 @@
break;
}
- // measure the hint's height to find how much more vertical space
- // we need to add to the drop down's height
- int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
- int heightSpec = MeasureSpec.UNSPECIFIED;
+ // Measure the hint's height to find how much more vertical
+ // space we need to add to the drop down's height.
+ final int widthSize;
+ final int widthMode;
+ if (mDropDownWidth >= 0) {
+ widthMode = MeasureSpec.AT_MOST;
+ widthSize = mDropDownWidth;
+ } else {
+ widthMode = MeasureSpec.UNSPECIFIED;
+ widthSize = 0;
+ }
+ final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
+ final int heightSpec = MeasureSpec.UNSPECIFIED;
hintView.measure(widthSpec, heightSpec);
hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
@@ -1629,6 +1658,11 @@
// This will call through to updateSelectorState()
drawableStateChanged();
+ final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+
if (mClickAnimation != null) {
mClickAnimation.cancel();
mClickAnimation = null;
@@ -1638,11 +1672,37 @@
private void setPressedItem(View child, int position, float x, float y) {
mDrawsInPressedState = true;
- // Ordering is essential. First update the pressed state and layout
- // the children. This will ensure the selector actually gets drawn.
- setPressed(true);
+ // Ordering is essential. First, update the container's pressed state.
+ if (Build.VERSION.SDK_INT >= 21) {
+ drawableHotspotChanged(x, y);
+ }
+ if (!isPressed()) {
+ setPressed(true);
+ }
+
+ // Next, run layout to stabilize child positions.
layoutChildren();
+ // Manage the pressed view based on motion position. This allows us to
+ // play nicely with actual touch and scroll events.
+ if (mMotionPosition != INVALID_POSITION) {
+ final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+ if (motionView != null && motionView != child && motionView.isPressed()) {
+ motionView.setPressed(false);
+ }
+ }
+ mMotionPosition = position;
+
+ // Offset for child coordinates.
+ final float childX = x - child.getLeft();
+ final float childY = y - child.getTop();
+ if (Build.VERSION.SDK_INT >= 21) {
+ child.drawableHotspotChanged(childX, childY);
+ }
+ if (!child.isPressed()) {
+ child.setPressed(true);
+ }
+
// Ensure that keyboard focus starts from the last touched position.
setSelection(position);
positionSelectorLikeTouchCompat(position, child, x, y);
@@ -1697,7 +1757,6 @@
public boolean hasFocus() {
return mHijackFocus || super.hasFocus();
}
-
}
private class PopupDataSetObserver extends DataSetObserver {
@@ -1723,8 +1782,9 @@
private class ResizePopupRunnable implements Runnable {
public void run() {
- if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
- mDropDownList.getChildCount() <= mListItemExpandMaximum) {
+ if (mDropDownList != null && ViewCompat.isAttachedToWindow(mDropDownList)
+ && mDropDownList.getCount() > mDropDownList.getChildCount()
+ && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
show();
}
diff --git a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
index 6aa55d7..19fe8ff 100644
--- a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
@@ -742,8 +742,9 @@
if (newState != oldState) {
playSoundEffect(SoundEffectConstants.CLICK);
- setChecked(newState);
}
+ // Always call setChecked so that the thumb is moved back to the correct edge
+ setChecked(newState);
cancelSuperTouch(ev);
}
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index 03f9f99..f21152f 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -63,7 +63,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-mediarouter-res \
- android-support-v7-appcompat
+ android-support-v7-appcompat \
+ android-support-v7-palette
include $(BUILD_STATIC_JAVA_LIBRARY)
# API Check
diff --git a/v7/mediarouter/api/current.txt b/v7/mediarouter/api/current.txt
index 95fc9d2..7a94542 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -567,7 +567,7 @@
method public java.util.List<android.support.v7.media.MediaRouter.RouteInfo> getRoutes();
}
- public static final class MediaRouter.RouteInfo {
+ public static class MediaRouter.RouteInfo {
method public boolean canDisconnect();
method public java.util.List<android.content.IntentFilter> getControlFilters();
method public java.lang.String getDescription();
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index a304746..0f680a5 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -5,6 +5,7 @@
dependencies {
compile project(':support-appcompat-v7')
+ compile project(':support-palette-v7')
}
// some of the source requires compiling against a newer API.
@@ -56,6 +57,11 @@
androidTest.java.srcDir 'tests/src'
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png
new file mode 100644
index 0000000..71db6b4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_audiotrack_light.png b/v7/mediarouter/res/drawable-hdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..d7c8252
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png
new file mode 100644
index 0000000..ceb1a1e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_close_light.png b/v7/mediarouter/res/drawable-hdpi/ic_close_light.png
new file mode 100644
index 0000000..9ab350e9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_close_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_down_black.png b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_down_black.png
new file mode 100644
index 0000000..3d7f83f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_down_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_down_white.png b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_down_white.png
new file mode 100644
index 0000000..bbb4fb4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_down_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_up_black.png b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_up_black.png
new file mode 100644
index 0000000..57139a7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_up_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_up_white.png b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_up_white.png
new file mode 100644
index 0000000..dea8988
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_keyboard_arrow_up_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png
deleted file mode 100644
index 3248ad1..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png
deleted file mode 100644
index c39fc1f..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png
new file mode 100755
index 0000000..0e1da44
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png
new file mode 100755
index 0000000..b90bb2f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_light.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_light.png
new file mode 100755
index 0000000..afdb9c1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png
new file mode 100755
index 0000000..e2c88be
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png
new file mode 100755
index 0000000..d1335f6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png b/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png
new file mode 100755
index 0000000..7330f56
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png
new file mode 100644
index 0000000..dc1200e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_audiotrack_light.png b/v7/mediarouter/res/drawable-mdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..2cf7e0c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png
new file mode 100644
index 0000000..af7f828
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_close_light.png b/v7/mediarouter/res/drawable-mdpi/ic_close_light.png
new file mode 100644
index 0000000..73faf52
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_close_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_down_black.png b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_down_black.png
new file mode 100644
index 0000000..5b1fa06
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_down_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_down_white.png b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_down_white.png
new file mode 100644
index 0000000..ef8a4b6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_down_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_up_black.png b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_up_black.png
new file mode 100644
index 0000000..08c16a3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_up_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_up_white.png b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_up_white.png
new file mode 100644
index 0000000..a2e4baa
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_keyboard_arrow_up_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png
deleted file mode 100644
index 1f0ba42..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png
deleted file mode 100644
index 3744fe4e..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_speaker_dark.png
new file mode 100755
index 0000000..7cc9845
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png
new file mode 100755
index 0000000..4db7209
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_light.png b/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_light.png
new file mode 100755
index 0000000..d26bb58
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-mdpi/ic_speaker_light.png
new file mode 100755
index 0000000..ae8d47f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png
new file mode 100755
index 0000000..82358a9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png b/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png
new file mode 100755
index 0000000..ba3f3d5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-xhdpi/ic_audiotrack.png
new file mode 100644
index 0000000..b5c899f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_audiotrack_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..4778e00
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png
new file mode 100644
index 0000000..b7c7ffd
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_close_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_close_light.png
new file mode 100644
index 0000000..a3896c5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_close_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_down_black.png b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_down_black.png
new file mode 100644
index 0000000..94016f4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_down_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_down_white.png b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_down_white.png
new file mode 100644
index 0000000..058cebb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_down_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_up_black.png b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_up_black.png
new file mode 100644
index 0000000..323360e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_up_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_up_white.png b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_up_white.png
new file mode 100644
index 0000000..ae36d91
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_keyboard_arrow_up_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png
deleted file mode 100644
index 8db5dbb..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png
deleted file mode 100644
index bfc30ef..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png
new file mode 100755
index 0000000..8f8a552
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png
new file mode 100755
index 0000000..6227ca2
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png
new file mode 100755
index 0000000..82599f5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png
new file mode 100755
index 0000000..74f9f6d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png
new file mode 100755
index 0000000..cef8ac5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_tv_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_tv_light.png
new file mode 100755
index 0000000..3131256
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_tv_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png
new file mode 100644
index 0000000..0546539
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..8e38265
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png
new file mode 100644
index 0000000..6b717e0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_close_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_close_light.png
new file mode 100644
index 0000000..22d7aa5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_close_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_down_black.png b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_down_black.png
new file mode 100644
index 0000000..17811ae
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_down_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_down_white.png b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_down_white.png
new file mode 100644
index 0000000..f9622b7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_down_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_up_black.png b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_up_black.png
new file mode 100644
index 0000000..748ed3f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_up_black.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_up_white.png b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_up_white.png
new file mode 100644
index 0000000..ce4aa56
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_keyboard_arrow_up_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png
deleted file mode 100644
index 1d58233..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png
deleted file mode 100644
index 43c9b99b..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png
new file mode 100755
index 0000000..874c961
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png
new file mode 100755
index 0000000..6869bdc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png
new file mode 100755
index 0000000..35de6f4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png
new file mode 100755
index 0000000..65ee187
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_tv_dark.png
new file mode 100755
index 0000000..a6a4858
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_tv_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_tv_light.png
new file mode 100755
index 0000000..4ca6787
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_tv_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable/mr_ic_settings_light.xml b/v7/mediarouter/res/drawable/mr_ic_close_dark.xml
similarity index 92%
copy from v7/mediarouter/res/drawable/mr_ic_settings_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_close_dark.xml
index a4614f6..4a88cec 100644
--- a/v7/mediarouter/res/drawable/mr_ic_settings_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_close_dark.xml
@@ -15,5 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@drawable/ic_setting_light" />
+ <item android:drawable="@drawable/ic_close_dark" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_settings_light.xml b/v7/mediarouter/res/drawable/mr_ic_close_light.xml
similarity index 92%
rename from v7/mediarouter/res/drawable/mr_ic_settings_light.xml
rename to v7/mediarouter/res/drawable/mr_ic_close_light.xml
index a4614f6..f1dd0cc 100644
--- a/v7/mediarouter/res/drawable/mr_ic_settings_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_close_light.xml
@@ -15,5 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@drawable/ic_setting_light" />
+ <item android:drawable="@drawable/ic_close_light" />
</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml b/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
deleted file mode 100644
index 0fe662e..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@drawable/ic_setting_dark" />
-</selector>
diff --git a/v7/mediarouter/res/layout-v17/mr_media_route_list_item.xml b/v7/mediarouter/res/layout-v17/mr_media_route_list_item.xml
deleted file mode 100644
index 1b798ee..0000000
--- a/v7/mediarouter/res/layout-v17/mr_media_route_list_item.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:gravity="center_vertical">
-
- <LinearLayout android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="vertical"
- android:gravity="start|center_vertical"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:duplicateParentState="true">
-
- <TextView android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:duplicateParentState="true" />
-
- <TextView android:id="@android:id/text2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:duplicateParentState="true" />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_chooser_dialog.xml b/v7/mediarouter/res/layout/mr_media_route_chooser_dialog.xml
index afdad71..6608f9a 100644
--- a/v7/mediarouter/res/layout/mr_media_route_chooser_dialog.xml
+++ b/v7/mediarouter/res/layout/mr_media_route_chooser_dialog.xml
@@ -17,25 +17,32 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:paddingTop="24dp"
+ android:paddingBottom="24dp">
+
<ListView android:id="@+id/media_route_list"
android:layout_width="fill_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="@dimen/mr_list_item_margin"/>
<LinearLayout android:id="@android:id/empty"
android:layout_width="fill_parent"
- android:layout_height="64dp"
- android:orientation="horizontal"
+ android:layout_height="240dp"
+ android:orientation="vertical"
+ android:paddingTop="90dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:visibility="gone">
- <ProgressBar android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:paddingLeft="16dp"
android:text="@string/mr_media_route_chooser_searching" />
+ <ProgressBar android:layout_width="150dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyleHorizontal" />
</LinearLayout>
</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml b/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
index 3e5f7d9..cfb3da4 100644
--- a/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
+++ b/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
@@ -23,29 +23,25 @@
<LinearLayout android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
android:orientation="horizontal" >
<TextView android:id="@+id/route_name"
android:layout_width="0dp"
android:layout_height="72dp"
android:layout_weight="1"
- android:layout_marginLeft="24dip"
- android:layout_marginRight="24dip"
android:gravity="center_vertical"
android:singleLine="true"
android:ellipsize="end"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorPrimary" />
- <ImageButton android:id="@+id/settings"
- android:layout_width="48dip"
- android:layout_height="48dip"
- android:padding="12dip"
- android:layout_marginTop="12dip"
- android:layout_marginBottom="12dip"
- android:layout_marginRight="12dip"
- android:contentDescription="@string/mr_media_route_controller_settings_description"
- android:src="?attr/mediaRouteSettingsDrawable"
- android:background="?attr/selectableItemBackgroundBorderless"
- android:visibility="gone" />
+ android:textAppearance="?attr/mediaRouteControllerTitleTextStyle" />
+ <ImageButton android:id="@+id/close"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginLeft="12dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/mr_media_route_controller_close_description"
+ android:src="?attr/mediaRouteCloseDrawable"
+ android:background="?attr/selectableItemBackgroundBorderless" />
</LinearLayout>
<FrameLayout android:id="@+id/media_route_control_frame"
android:layout_width="match_parent"
@@ -53,13 +49,12 @@
<RelativeLayout android:id="@+id/default_control_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/colorPrimary" >
+ android:background="?attr/colorPrimary">
<ImageView android:id="@+id/art"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:maxHeight="@dimen/mr_media_route_controller_art_max_height"
android:adjustViewBounds="true"
- android:scaleType="centerCrop"/>
+ android:scaleType="fitCenter"/>
<ImageButton android:id="@+id/play_pause"
android:layout_width="48dip"
android:layout_height="48dip"
@@ -82,17 +77,12 @@
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
- android:textStyle="bold"
+ android:textAppearance="?attr/mediaRouteControllerPrimaryTextStyle"
android:singleLine="true" />
<TextView android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"
+ android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle"
android:singleLine="true" />
</LinearLayout>
</RelativeLayout>
@@ -103,7 +93,8 @@
android:layout_height="64dp"
android:gravity="center_vertical"
android:padding="8dp"
- android:visibility="gone">
+ android:visibility="gone"
+ android:background="?attr/colorPrimary">
<ImageView android:layout_width="48dp"
android:layout_height="48dp"
android:src="?attr/mediaRouteCastDrawable"
@@ -115,7 +106,20 @@
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" />
+ <ImageButton android:id="@+id/media_route_group_expand_collapse"
+ android:layout_width="48dip"
+ android:layout_height="48dip"
+ android:padding="12dip"
+ android:contentDescription="@string/mr_media_route_controller_expand_group"
+ android:src="?attr/mediaRouteExpandGroupDrawable"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:visibility="gone"/>
</LinearLayout>
+ <ListView android:id="@+id/media_route_volume_group_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ </ListView>
<LinearLayout android:id="@+id/buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/v7/mediarouter/res/layout/mr_media_route_controller_volume_item.xml b/v7/mediarouter/res/layout/mr_media_route_controller_volume_item.xml
new file mode 100644
index 0000000..d07d94b
--- /dev/null
+++ b/v7/mediarouter/res/layout/mr_media_route_controller_volume_item.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="64dp"
+ android:padding="8dp">
+ <TextView android:id="@+id/media_route_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true" />
+ <ImageView android:id="@+id/media_route_volume_item_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_below="@id/media_route_name"
+ android:layout_alignParentLeft="true"
+ android:src="?attr/mediaRouteAudioTrackDrawable"
+ android:gravity="center"
+ android:scaleType="center" />
+ <SeekBar android:id="@+id/media_route_volume_slider"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/media_route_name"
+ android:layout_toRightOf="@id/media_route_volume_item_icon"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="8dp"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp" />
+</RelativeLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_list_item.xml b/v7/mediarouter/res/layout/mr_media_route_list_item.xml
index 6c63a12..a2804ae 100644
--- a/v7/mediarouter/res/layout/mr_media_route_list_item.xml
+++ b/v7/mediarouter/res/layout/mr_media_route_list_item.xml
@@ -16,32 +16,38 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="64dp"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
android:gravity="center_vertical">
+ <ImageView android:id="@+id/routeIcon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginLeft="24dp"
+ android:layout_marginRight="24dp" />
+
<LinearLayout android:layout_width="0dp"
- android:layout_height="fill_parent"
+ android:layout_height="wrap_content"
android:layout_weight="1"
+ android:layout_marginRight="24dp"
android:orientation="vertical"
android:gravity="left|center_vertical"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
android:duplicateParentState="true">
<TextView android:id="@android:id/text1"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
+ android:layout_height="32dp"
android:singleLine="true"
android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?attr/mediaRouteChooserPrimaryTextStyle"
android:duplicateParentState="true" />
<TextView android:id="@android:id/text2"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
+ android:layout_height="24dp"
android:singleLine="true"
android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAppearance="?attr/mediaRouteChooserSecondaryTextStyle"
android:duplicateParentState="true" />
</LinearLayout>
diff --git a/v7/mediarouter/res/values-az-rAZ/strings.xml b/v7/mediarouter/res/values-az-rAZ/strings.xml
deleted file mode 100644
index 2f3ff74..0000000
--- a/v7/mediarouter/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2013 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
- <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
- <string name="mr_media_route_button_content_description" msgid="8327680881775995150">"İştirakçılar"</string>
- <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Cihaza qoş"</string>
- <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Cihazları axtarır..."</string>
- <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Bağlantını kəs"</string>
- <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Yayımı dayandır"</string>
- <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Marşrut parametrləri"</string>
- <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Göstər"</string>
- <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Fasilə ver"</string>
-</resources>
diff --git a/v7/mediarouter/res/values/attrs.xml b/v7/mediarouter/res/values/attrs.xml
index 9f5f8ac..fe01948 100644
--- a/v7/mediarouter/res/values/attrs.xml
+++ b/v7/mediarouter/res/values/attrs.xml
@@ -30,8 +30,20 @@
<attr name="mediaRouteOffDrawable" format="reference" />
<attr name="mediaRouteConnectingDrawable" format="reference" />
<attr name="mediaRouteOnDrawable" format="reference" />
- <attr name="mediaRouteSettingsDrawable" format="reference" />
+ <attr name="mediaRouteCloseDrawable" format="reference" />
<attr name="mediaRoutePlayDrawable" format="reference" />
<attr name="mediaRoutePauseDrawable" format="reference" />
<attr name="mediaRouteCastDrawable" format="reference" />
-</resources>
\ No newline at end of file
+ <attr name="mediaRouteAudioTrackDrawable" format="reference" />
+ <attr name="mediaRouteDefaultIconDrawable" format="reference" />
+ <attr name="mediaRouteSpeakerIconDrawable" format="reference" />
+ <attr name="mediaRouteSpeakerGroupIconDrawable" format="reference" />
+ <attr name="mediaRouteChooserPrimaryTextStyle" format="reference" />
+ <attr name="mediaRouteChooserSecondaryTextStyle" format="reference" />
+ <attr name="mediaRouteControllerTitleTextStyle" format="reference" />
+ <attr name="mediaRouteControllerPrimaryTextStyle" format="reference" />
+ <attr name="mediaRouteControllerSecondaryTextStyle" format="reference" />
+ <attr name="mediaRouteExpandGroupDrawable" format="reference" />
+ <attr name="mediaRouteCollapseGroupDrawable" format="reference" />
+ <attr name="mediaRouteControllerTextStyle" format="reference" />
+</resources>
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
index e687c82..d253f4c 100644
--- a/v7/mediarouter/res/values/dimens.xml
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -16,4 +16,5 @@
<resources>
<dimen name="mr_media_route_controller_art_max_height">320dp</dimen>
-</resources>
\ No newline at end of file
+ <dimen name="mr_list_item_margin">24dp</dimen>
+</resources>
diff --git a/v7/mediarouter/res/values/strings.xml b/v7/mediarouter/res/values/strings.xml
index a87ce4f..9fc2f2e 100644
--- a/v7/mediarouter/res/values/strings.xml
+++ b/v7/mediarouter/res/values/strings.xml
@@ -26,10 +26,10 @@
<string name="mr_media_route_button_content_description">Cast</string>
<!-- Title of the media route chooser dialog. [CHAR LIMIT=30] -->
- <string name="mr_media_route_chooser_title">Connect to device</string>
+ <string name="mr_media_route_chooser_title">Cast to</string>
<!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] -->
- <string name="mr_media_route_chooser_searching">Searching for devices\u2026</string>
+ <string name="mr_media_route_chooser_searching">Finding devices</string>
<!-- Button to disconnect from a media route. [CHAR LIMIT=30] -->
<string name="mr_media_route_controller_disconnect">Disconnect</string>
@@ -37,8 +37,8 @@
<!-- Button to stop playback and disconnect from a media route. [CHAR LIMIT=30] -->
<string name="mr_media_route_controller_stop">Stop casting</string>
- <!-- Description for a button that takes you to settings for the active route -->
- <string name="mr_media_route_controller_settings_description">Route settings</string>
+ <!-- Description for a button that closes the controller dialog -->
+ <string name="mr_media_route_controller_close_description">Close the controller dialog</string>
<!-- Accessibility description for the play button -->
<string name="mr_media_route_controller_play">Play</string>
@@ -46,6 +46,12 @@
<!-- Accessibility description for the pause button -->
<string name="mr_media_route_controller_pause">Pause</string>
+ <!-- Accessibility description for the collapse group button -->
+ <string name="mr_media_route_controller_collapse_group">Collapse group</string>
+
+ <!-- Accessibility description for the expand group button -->
+ <string name="mr_media_route_controller_expand_group">Expand group</string>
+
<!-- Placeholder text to show when no title/description have been found for a given song/video. [CHAR LIMIT=50] -->
<string name="mr_media_route_controller_no_info_available">No info available</string>
</resources>
diff --git a/v7/mediarouter/res/values/styles.xml b/v7/mediarouter/res/values/styles.xml
index 9be8545..cebe09a 100644
--- a/v7/mediarouter/res/values/styles.xml
+++ b/v7/mediarouter/res/values/styles.xml
@@ -17,21 +17,83 @@
<resources>
<style name="Widget.MediaRouter.MediaRouteButton"
parent="Widget.AppCompat.ActionButton">
- <item name="android:minWidth">56dp</item>
- <item name="android:minHeight">48dp</item>
- <item name="android:padding">0dp</item>
- <item name="android:focusable">true</item>
<item name="android:contentDescription">@string/mr_media_route_button_content_description</item>
<item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_mono_dark</item>
</style>
<style name="Widget.MediaRouter.Light.MediaRouteButton"
parent="Widget.AppCompat.Light.ActionButton">
- <item name="android:minWidth">56dp</item>
- <item name="android:minHeight">48dp</item>
- <item name="android:padding">0dp</item>
- <item name="android:focusable">true</item>
<item name="android:contentDescription">@string/mr_media_route_button_content_description</item>
<item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_mono_light</item>
</style>
-</resources>
\ No newline at end of file
+
+ <!-- MediaRouteChooserDialog text styles -->
+ <style name="Widget.MediaRouter.ChooserText" parent="">
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:textStyle">normal</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ChooserText.Primary">
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ChooserText.Secondary">
+ <item name="android:textSize">14sp</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ChooserText.Primary.Dark">
+ <item name="android:textColor">#FFFFFFFF</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ChooserText.Primary.Light">
+ <item name="android:textColor">#DE000000</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ChooserText.Secondary.Dark">
+ <item name="android:textColor">#8AFFFFFF</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ChooserText.Secondary.Light">
+ <item name="android:textColor">#8A000000</item>
+ </style>
+
+ <!-- MediaRouteControllerDialog text styles -->
+ <style name="Widget.MediaRouter.ControllerText" parent="Widget.MediaRouter.ChooserText" />
+
+ <style name="Widget.MediaRouter.ControllerText.Title">
+ <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:textSize">20sp</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Primary">
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Secondary">
+ <item name="android:textSize">14sp</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Title.Dark">
+ <item name="android:textColor">#FFFFFFFF</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Title.Light">
+ <item name="android:textColor">#DE000000</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Primary.Dark">
+ <item name="android:textColor">#FFFFFFFF</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Primary.Light">
+ <item name="android:textColor">#DE000000</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Secondary.Dark">
+ <item name="android:textColor">#FFFFFFFF</item>
+ </style>
+
+ <style name="Widget.MediaRouter.ControllerText.Secondary.Light">
+ <item name="android:textColor">#DE000000</item>
+ </style>
+</resources>
diff --git a/v7/mediarouter/res/values/themes.xml b/v7/mediarouter/res/values/themes.xml
index 85d0a8b..490e04e 100644
--- a/v7/mediarouter/res/values/themes.xml
+++ b/v7/mediarouter/res/values/themes.xml
@@ -17,27 +17,50 @@
<resources>
<style name="Theme.MediaRouter" parent="">
+ <item name="android:windowNoTitle">false</item>
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
<item name="mediaRouteOffDrawable">@drawable/ic_media_route_off_mono_dark</item>
<item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_dark</item>
<item name="mediaRouteOnDrawable">@drawable/ic_media_route_on_mono_dark</item>
- <item name="mediaRouteSettingsDrawable">@drawable/mr_ic_settings_dark</item>
+ <item name="mediaRouteCloseDrawable">@drawable/mr_ic_close_dark</item>
<item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_dark</item>
<item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_dark</item>
<item name="mediaRouteCastDrawable">@drawable/mr_ic_cast_dark</item>
+ <item name="mediaRouteAudioTrackDrawable">@drawable/ic_audiotrack</item>
+ <item name="mediaRouteDefaultIconDrawable">@drawable/ic_tv_dark</item>
+ <item name="mediaRouteSpeakerIconDrawable">@drawable/ic_speaker_dark</item>
+ <item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_speaker_group_dark</item>
+ <item name="mediaRouteChooserPrimaryTextStyle">@style/Widget.MediaRouter.ChooserText.Primary.Dark</item>
+ <item name="mediaRouteChooserSecondaryTextStyle">@style/Widget.MediaRouter.ChooserText.Secondary.Dark</item>
+ <item name="mediaRouteControllerTitleTextStyle">@style/Widget.MediaRouter.ControllerText.Title.Dark</item>
+ <item name="mediaRouteControllerPrimaryTextStyle">@style/Widget.MediaRouter.ControllerText.Primary.Dark</item>
+ <item name="mediaRouteControllerSecondaryTextStyle">@style/Widget.MediaRouter.ControllerText.Secondary.Dark</item>
+ <item name="mediaRouteExpandGroupDrawable">@drawable/ic_keyboard_arrow_down_white</item>
+ <item name="mediaRouteCollapseGroupDrawable">@drawable/ic_keyboard_arrow_up_white</item>
</style>
- <style name="Theme.MediaRouter.Light" parent="">
+ <style name="Theme.MediaRouter.Light">
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
<item name="mediaRouteOffDrawable">@drawable/ic_cast_off_light</item>
<item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_light</item>
<item name="mediaRouteOnDrawable">@drawable/ic_cast_on_light</item>
- <item name="mediaRouteSettingsDrawable">@drawable/mr_ic_settings_light</item>
+ <item name="mediaRouteCloseDrawable">@drawable/mr_ic_close_light</item>
<item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_light</item>
<item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_light</item>
<item name="mediaRouteCastDrawable">@drawable/mr_ic_cast_light</item>
+ <item name="mediaRouteAudioTrackDrawable">@drawable/ic_audiotrack_light</item>
+ <item name="mediaRouteDefaultIconDrawable">@drawable/ic_tv_light</item>
+ <item name="mediaRouteSpeakerIconDrawable">@drawable/ic_speaker_light</item>
+ <item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_speaker_group_light</item>
+ <item name="mediaRouteChooserPrimaryTextStyle">@style/Widget.MediaRouter.ChooserText.Primary.Light</item>
+ <item name="mediaRouteChooserSecondaryTextStyle">@style/Widget.MediaRouter.ChooserText.Secondary.Light</item>
+ <item name="mediaRouteControllerTitleTextStyle">@style/Widget.MediaRouter.ControllerText.Title.Light</item>
+ <item name="mediaRouteControllerPrimaryTextStyle">@style/Widget.MediaRouter.ControllerText.Primary.Light</item>
+ <item name="mediaRouteControllerSecondaryTextStyle">@style/Widget.MediaRouter.ControllerText.Secondary.Light</item>
+ <item name="mediaRouteExpandGroupDrawable">@drawable/ic_keyboard_arrow_down_black</item>
+ <item name="mediaRouteCollapseGroupDrawable">@drawable/ic_keyboard_arrow_up_black</item>
</style>
</resources>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index 379ba80..9fdd56c 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -431,40 +431,40 @@
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- final int minWidth = Math.max(mMinWidth,
- mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicWidth() : 0);
- final int minHeight = Math.max(mMinHeight,
- mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicHeight() : 0);
+ final int width = Math.max(mMinWidth, mRemoteIndicator != null ?
+ mRemoteIndicator.getIntrinsicWidth() + getPaddingLeft() + getPaddingRight() : 0);
+ final int height = Math.max(mMinHeight, mRemoteIndicator != null ?
+ mRemoteIndicator.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom() : 0);
- int width;
+ int measuredWidth;
switch (widthMode) {
case MeasureSpec.EXACTLY:
- width = widthSize;
+ measuredWidth = widthSize;
break;
case MeasureSpec.AT_MOST:
- width = Math.min(widthSize, minWidth + getPaddingLeft() + getPaddingRight());
+ measuredWidth = Math.min(widthSize, width);
break;
default:
case MeasureSpec.UNSPECIFIED:
- width = minWidth + getPaddingLeft() + getPaddingRight();
+ measuredWidth = width;
break;
}
- int height;
+ int measuredHeight;
switch (heightMode) {
case MeasureSpec.EXACTLY:
- height = heightSize;
+ measuredHeight = heightSize;
break;
case MeasureSpec.AT_MOST:
- height = Math.min(heightSize, minHeight + getPaddingTop() + getPaddingBottom());
+ measuredHeight = Math.min(heightSize, height);
break;
default:
case MeasureSpec.UNSPECIFIED:
- height = minHeight + getPaddingTop() + getPaddingBottom();
+ measuredHeight = height;
break;
}
- setMeasuredDimension(width, height);
+ setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index 779ae8b..8b5d6fc 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -16,26 +16,41 @@
package android.support.v7.app;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+
import android.app.Dialog;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
+import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
-import android.support.v7.media.MediaRouter;
+import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
import android.support.v7.mediarouter.R;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
/**
@@ -48,6 +63,8 @@
* @see MediaRouteActionProvider
*/
public class MediaRouteChooserDialog extends Dialog {
+ private static final String TAG = "MediaRouteChooserDialog";
+
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
@@ -138,16 +155,9 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
-
setContentView(R.layout.mr_media_route_chooser_dialog);
setTitle(R.string.mr_media_route_chooser_title);
- // Must be called after setContentView.
- getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
- MediaRouterThemeHelper.getThemeResource(
- getContext(), R.attr.mediaRouteOffDrawable));
-
mRoutes = new ArrayList<MediaRouter.RouteInfo>();
mAdapter = new RouteAdapter(getContext(), mRoutes);
mListView = (ListView)findViewById(R.id.media_route_list);
@@ -181,6 +191,7 @@
mRoutes.clear();
mRoutes.addAll(mRouter.getRoutes());
onFilterRoutes(mRoutes);
+ RouteComparator.loadRouteUsageScores(getContext(), mRoutes);
Collections.sort(mRoutes, RouteComparator.sInstance);
mAdapter.notifyDataSetChanged();
}
@@ -189,10 +200,21 @@
private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
implements ListView.OnItemClickListener {
private final LayoutInflater mInflater;
+ private final Drawable mDefaultIcon;
+ private final Drawable mSpeakerIcon;
+ private final Drawable mSpeakerGroupIcon;
public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
super(context, 0, routes);
mInflater = LayoutInflater.from(context);
+ TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
+ R.attr.mediaRouteDefaultIconDrawable,
+ R.attr.mediaRouteSpeakerIconDrawable,
+ R.attr.mediaRouteSpeakerGroupIconDrawable });
+ mDefaultIcon = styledAttributes.getDrawable(0);
+ mSpeakerIcon = styledAttributes.getDrawable(1);
+ mSpeakerGroupIcon = styledAttributes.getDrawable(2);
+ styledAttributes.recycle();
}
@Override
@@ -211,19 +233,30 @@
if (view == null) {
view = mInflater.inflate(R.layout.mr_media_route_list_item, parent, false);
}
+
MediaRouter.RouteInfo route = getItem(position);
- TextView text1 = (TextView)view.findViewById(android.R.id.text1);
- TextView text2 = (TextView)view.findViewById(android.R.id.text2);
+ TextView text1 = (TextView) view.findViewById(android.R.id.text1);
+ TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText(route.getName());
String description = route.getDescription();
- if (TextUtils.isEmpty(description)) {
- text2.setVisibility(View.GONE);
- text2.setText("");
- } else {
+ boolean isConnectedOrConnecting =
+ route.getConnectionState() == CONNECTION_STATE_CONNECTED
+ || route.getConnectionState() == CONNECTION_STATE_CONNECTING;
+ if (isConnectedOrConnecting && !TextUtils.isEmpty(description)) {
+ text1.setGravity(Gravity.BOTTOM);
text2.setVisibility(View.VISIBLE);
text2.setText(description);
+ } else {
+ text1.setGravity(Gravity.CENTER_VERTICAL);
+ text2.setVisibility(View.GONE);
+ text2.setText("");
}
view.setEnabled(route.isEnabled());
+
+ ImageView iconView = (ImageView) view.findViewById(R.id.routeIcon);
+ if (iconView != null) {
+ iconView.setImageDrawable(getIconDrawable(route));
+ }
return view;
}
@@ -232,9 +265,39 @@
MediaRouter.RouteInfo route = getItem(position);
if (route.isEnabled()) {
route.select();
+ RouteComparator.storeRouteUsageScores(getContext(), route.getId());
dismiss();
}
}
+
+ private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+ Uri iconUri = route.getIconUri();
+ if (iconUri != null) {
+ try {
+ InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+ Drawable drawable = Drawable.createFromStream(is, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load " + iconUri, e);
+ // Falls back.
+ }
+ }
+ return getDefaultIconDrawable(route);
+ }
+
+ private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+ if (route instanceof MediaRouter.RouteGroup) {
+ // Only speakers can be grouped for now.
+ return mSpeakerGroupIcon;
+ }
+ if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
+ return mSpeakerIcon;
+ }
+ return mDefaultIcon;
+ }
}
private final class MediaRouterCallback extends MediaRouter.Callback {
@@ -260,11 +323,96 @@
}
private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+ private static final String PREF_ROUTE_IDS =
+ "android.support.v7.app.MediaRouteChooserDialog_route_ids";
+ private static final String PREF_USAGE_SCORE_PREFIX =
+ "android.support.v7.app.MediaRouteChooserDialog_route_usage_score_";
+ // Routes with the usage score less than MIN_USAGE_SCORE are decayed.
+ private static final float MIN_USAGE_SCORE = 0.1f;
+ private static final float USAGE_SCORE_DECAY_FACTOR = 0.95f;
+
+ // Should match to SystemMediaRouteProvider.PACKAGE_NAME.
+ static final String SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME = "android";
+
public static final RouteComparator sInstance = new RouteComparator();
+ public static final HashMap<String, Float> sRouteUsageScoreMap = new HashMap();
@Override
public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+ if (isSystemLiveAudioOnlyRoute(lhs)) {
+ if (!isSystemLiveAudioOnlyRoute(rhs)) {
+ return 1;
+ }
+ } else if (isSystemLiveAudioOnlyRoute(rhs)) {
+ return -1;
+ }
+ Float lhsUsageScore = sRouteUsageScoreMap.get(lhs.getId());
+ if (lhsUsageScore == null) {
+ lhsUsageScore = 0f;
+ }
+ Float rhsUsageScore = sRouteUsageScoreMap.get(rhs.getId());
+ if (rhsUsageScore == null) {
+ rhsUsageScore = 0f;
+ }
+ if (!lhsUsageScore.equals(rhsUsageScore)) {
+ return lhsUsageScore > rhsUsageScore ? -1 : 1;
+ }
return lhs.getName().compareTo(rhs.getName());
}
+
+ private boolean isSystemLiveAudioOnlyRoute(MediaRouter.RouteInfo route) {
+ return isSystemMediaRouteProvider(route)
+ && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+ }
+
+ private boolean isSystemMediaRouteProvider(MediaRouter.RouteInfo route) {
+ return TextUtils.equals(route.getProviderInstance().getMetadata().getPackageName(),
+ SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME);
+ }
+
+ private static void loadRouteUsageScores(
+ Context context, List<MediaRouter.RouteInfo> routes) {
+ sRouteUsageScoreMap.clear();
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ for (MediaRouter.RouteInfo route : routes) {
+ sRouteUsageScoreMap.put(route.getId(),
+ preferences.getFloat(PREF_USAGE_SCORE_PREFIX + route.getId(), 0f));
+ }
+ }
+
+ private static void storeRouteUsageScores(Context context, String selectedRouteId) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor prefEditor = preferences.edit();
+ List<String> routeIds = new ArrayList<String>(
+ Arrays.asList(preferences.getString(PREF_ROUTE_IDS, "").split(",")));
+ if (!routeIds.contains(selectedRouteId)) {
+ routeIds.add(selectedRouteId);
+ }
+ StringBuilder routeIdsBuilder = new StringBuilder();
+ for (String routeId : routeIds) {
+ // The new route usage score is calculated as follows:
+ // 1) usageScore * USAGE_SCORE_DECAY_FACTOR + 1, if the route is selected,
+ // 2) 0, if usageScore * USAGE_SCORE_DECAY_FACTOR < MIN_USAGE_SCORE, or
+ // 3) usageScore * USAGE_SCORE_DECAY_FACTOR, otherwise,
+ String routeUsageScoreKey = PREF_USAGE_SCORE_PREFIX + routeId;
+ float newUsageScore = preferences.getFloat(routeUsageScoreKey, 0f)
+ * USAGE_SCORE_DECAY_FACTOR;
+ if (selectedRouteId.equals(routeId)) {
+ newUsageScore += 1f;
+ }
+ if (newUsageScore < MIN_USAGE_SCORE) {
+ prefEditor.remove(routeId);
+ } else {
+ prefEditor.putFloat(routeUsageScoreKey, newUsageScore);
+ if (routeIdsBuilder.length() > 0) {
+ routeIdsBuilder.append(',');
+ }
+ routeIdsBuilder.append(routeId);
+ }
+ }
+ prefEditor.putString(PREF_ROUTE_IDS, routeIdsBuilder.toString());
+ prefEditor.commit();
+ }
}
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index f98e6b2..5fc84588 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -16,9 +16,17 @@
package android.support.v7.app;
+import static android.widget.SeekBar.OnSeekBarChangeListener;
+
+import android.content.ContentResolver;
import android.content.Context;
import android.content.IntentSender;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.media.MediaDescriptionCompat;
@@ -26,21 +34,30 @@
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v7.graphics.Palette;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.mediarouter.R;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.util.List;
+
/**
* This class implements the route controller dialog for {@link MediaRouter}.
* <p>
@@ -53,6 +70,9 @@
public class MediaRouteControllerDialog extends AlertDialog {
private static final String TAG = "MediaRouteControllerDialog";
+ // STOPSHIP: Remove the flag when the group volume control implementation completes.
+ private static final boolean USE_GROUP = false;
+
// Time to wait before updating the volume when the user lets go of the seek bar
// to allow the route provider time to propagate the change and publish a new
// route descriptor.
@@ -64,15 +84,14 @@
private boolean mCreated;
private boolean mAttachedToWindow;
- private Drawable mMediaRouteConnectingDrawable;
- private Drawable mMediaRouteOnDrawable;
private View mControlView;
private Button mDisconnectButton;
private Button mStopCastingButton;
private ImageButton mPlayPauseButton;
- private ImageButton mSettingsButton;
+ private ImageButton mCloseButton;
+ private ImageButton mGroupExpandCollapseButton;
private ImageView mArtView;
private TextView mTitleView;
@@ -81,6 +100,7 @@
private boolean mVolumeControlEnabled = true;
private LinearLayout mVolumeLayout;
+ private ListView mVolumeGroupList;
private SeekBar mVolumeSlider;
private boolean mVolumeSliderTouched;
@@ -89,6 +109,7 @@
private PlaybackStateCompat mState;
private MediaDescriptionCompat mDescription;
+ private FetchArtTask mFetchArtTask;
public MediaRouteControllerDialog(Context context) {
this(context, 0);
@@ -112,6 +133,13 @@
return mRoute;
}
+ private MediaRouter.RouteGroup getGroup() {
+ if (mRoute instanceof MediaRouter.RouteGroup) {
+ return (MediaRouter.RouteGroup) mRoute;
+ }
+ return null;
+ }
+
/**
* Provides the subclass an opportunity to create a view that will
* be included within the body of the dialog to offer additional media controls
@@ -213,8 +241,8 @@
mStopCastingButton = (Button) findViewById(R.id.stop);
mStopCastingButton.setOnClickListener(listener);
- mSettingsButton = (ImageButton) findViewById(R.id.settings);
- mSettingsButton.setOnClickListener(listener);
+ mCloseButton = (ImageButton) findViewById(R.id.close);
+ mCloseButton.setOnClickListener(listener);
mArtView = (ImageView) findViewById(R.id.art);
mTitleView = (TextView) findViewById(R.id.title);
@@ -223,6 +251,34 @@
mPlayPauseButton.setOnClickListener(listener);
mRouteNameView = (TextView) findViewById(R.id.route_name);
mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout);
+ mVolumeGroupList = (ListView)findViewById(R.id.media_route_volume_group_list);
+
+ TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
+ R.attr.mediaRouteExpandGroupDrawable,
+ R.attr.mediaRouteCollapseGroupDrawable });
+ final Drawable expandGroupDrawable = styledAttributes.getDrawable(0);
+ final Drawable collapseGroupDrawable = styledAttributes.getDrawable(1);
+ styledAttributes.recycle();
+
+ mGroupExpandCollapseButton = (ImageButton)findViewById(
+ R.id.media_route_group_expand_collapse);
+ mGroupExpandCollapseButton.setOnClickListener(new View.OnClickListener() {
+ private boolean mIsExpanded;
+
+ @Override
+ public void onClick(View v) {
+ mIsExpanded = !mIsExpanded;
+ if (mIsExpanded) {
+ mGroupExpandCollapseButton.setImageDrawable(collapseGroupDrawable);
+ mVolumeGroupList.setVisibility(View.VISIBLE);
+ mVolumeGroupList.setAdapter(
+ new VolumeGroupAdapter(getContext(), getGroup().getRoutes()));
+ } else {
+ mGroupExpandCollapseButton.setImageDrawable(expandGroupDrawable);
+ mVolumeGroupList.setVisibility(View.GONE);
+ }
+ }
+ });
mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider);
mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
private final Runnable mStopTrackingTouch = new Runnable() {
@@ -329,23 +385,18 @@
}
if (mRoute.getSettingsIntent() != null) {
- mSettingsButton.setVisibility(View.VISIBLE);
+ mCloseButton.setVisibility(View.VISIBLE);
} else {
- mSettingsButton.setVisibility(View.GONE);
+ mCloseButton.setVisibility(View.GONE);
}
if (mControlView == null) {
- if (mDescription != null && mDescription.getIconBitmap() != null) {
- mArtView.setImageBitmap(mDescription.getIconBitmap());
- mArtView.setVisibility(View.VISIBLE);
- } else if (mDescription != null && mDescription.getIconUri() != null) {
- // TODO replace with background load of icon
- mArtView.setImageURI(mDescription.getIconUri());
- mArtView.setVisibility(View.VISIBLE);
- } else {
- mArtView.setImageDrawable(null);
- mArtView.setVisibility(View.GONE);
+ if (mFetchArtTask != null) {
+ mFetchArtTask.cancel(true);
}
+ mArtView.setVisibility(View.GONE);
+ mFetchArtTask = new FetchArtTask();
+ mFetchArtTask.execute();
CharSequence title = mDescription == null ? null : mDescription.getTitle();
boolean hasTitle = !TextUtils.isEmpty(title);
@@ -394,28 +445,16 @@
return true;
}
- private Drawable getIconDrawable() {
- if (mRoute.isConnecting()) {
- if (mMediaRouteConnectingDrawable == null) {
- mMediaRouteConnectingDrawable = MediaRouterThemeHelper.getThemeDrawable(
- getContext(), R.attr.mediaRouteConnectingDrawable);
- }
- return mMediaRouteConnectingDrawable;
- } else {
- if (mMediaRouteOnDrawable == null) {
- mMediaRouteOnDrawable = MediaRouterThemeHelper.getThemeDrawable(
- getContext(), R.attr.mediaRouteOnDrawable);
- }
- return mMediaRouteOnDrawable;
- }
- }
-
private void updateVolume() {
if (!mVolumeSliderTouched) {
if (isVolumeControlAvailable()) {
mVolumeLayout.setVisibility(View.VISIBLE);
mVolumeSlider.setMax(mRoute.getVolumeMax());
mVolumeSlider.setProgress(mRoute.getVolume());
+ if (USE_GROUP) {
+ mGroupExpandCollapseButton.setVisibility(
+ getGroup() != null ? View.VISIBLE : View.GONE);
+ }
} else {
mVolumeLayout.setVisibility(View.GONE);
}
@@ -487,17 +526,167 @@
mMediaController.getTransportControls().play();
}
}
- } else if (id == R.id.settings) {
- IntentSender is = mRoute.getSettingsIntent();
- if (is != null) {
+ } else if (id == R.id.close) {
+ dismiss();
+ }
+ }
+ }
+
+ private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
+ final OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ int position = (int) seekBar.getTag();
+ // TODO: Verify
+ getGroup().getRouteAt(position).requestSetVolume(progress);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // TODO: Implement
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // TODO: Implement
+ }
+ };
+
+ public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
+ super(context, 0, objects);
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ View v = convertView;
+ if (v == null) {
+ v = LayoutInflater.from(getContext()).inflate(
+ R.layout.mr_media_route_controller_volume_item, null);
+ }
+ MediaRouter.RouteInfo route = getItem(position);
+ if (route != null) {
+ TextView textView = (TextView) v.findViewById(R.id.media_route_name);
+ textView.setText(route.getName());
+
+ SeekBar volumeSlider = (SeekBar) v.findViewById(R.id.media_route_volume_slider);
+ if (route.getVolumeHandling() == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+ volumeSlider.setMax(route.getVolumeMax());
+ volumeSlider.setProgress(route.getVolume());
+ volumeSlider.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+ } else {
+ volumeSlider.setMax(100);
+ volumeSlider.setProgress(100);
+ volumeSlider.setEnabled(false);
+ }
+ volumeSlider.setTag(position);
+ }
+ return v;
+ }
+ }
+
+ private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
+ private int mBackgroundColor;
+
+ @Override
+ protected Bitmap doInBackground(Void... arg) {
+ Bitmap bitmap = null;
+ if (mDescription == null) {
+ return null;
+ }
+ if (mDescription.getIconBitmap() != null) {
+ bitmap = mDescription.getIconBitmap();
+ } else if (mDescription.getIconUri() != null) {
+ Uri iconUri = mDescription.getIconUri();
+ String scheme = iconUri.getScheme();
+ if (!(ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme))) {
+ Log.w(TAG, "Icon Uri should point to local resources.");
+ return null;
+ }
+ BufferedInputStream stream = null;
+ try {
+ stream = new BufferedInputStream(
+ getContext().getContentResolver().openInputStream(iconUri));
+
+ // Query bitmap size.
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(stream, null, options);
+
+ // Rewind the stream in order to restart bitmap decoding.
try {
- is.sendIntent(null, 0, null, null, null);
- dismiss();
- } catch (Exception e) {
- Log.e(TAG, "Error opening route settings.", e);
+ stream.reset();
+ } catch (IOException e) {
+ // Failed to rewind the stream, try to reopen it.
+ stream.close();
+ stream = new BufferedInputStream(getContext().getContentResolver()
+ .openInputStream(iconUri));
+ }
+
+ // Caculate required size to decode the bitmap and possibly resize it.
+ options.inJustDecodeBounds = false;
+ int reqWidth;
+ int reqHeight;
+ if (options.outWidth >= options.outHeight) {
+ // For landscape image, fit width to dialog width.
+ reqWidth = getWindow().getDecorView().getWidth();
+ reqHeight = reqWidth * (options.outHeight / options.outWidth);
+ } else {
+ // For portrait image, fit height to 16:9 ratio case's height.
+ reqHeight = getWindow().getDecorView().getWidth() * 9 / 16;
+ reqWidth = reqHeight * (options.outWidth / options.outHeight);
+ }
+ int ratio = Math.max(
+ options.outWidth / reqWidth, options.outHeight / reqHeight);
+ options.inSampleSize = Math.max(1, Integer.highestOneBit(ratio));
+ if (isCancelled()) {
+ return null;
+ }
+ bitmap = BitmapFactory.decodeStream(stream, null, options);
+ } catch (IOException e){
+ Log.w(TAG, "Unable to open content: " + iconUri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
}
}
}
+ if (bitmap != null) {
+ if (bitmap.getWidth() < bitmap.getHeight()) {
+ // Portrait image requires background color.
+ mBackgroundColor =
+ new Palette.Builder(bitmap).generate().getDarkVibrantColor(0);
+ }
+ }
+ return bitmap;
+ }
+
+ @Override
+ protected void onCancelled() {
+ mFetchArtTask = null;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ mFetchArtTask = null;
+ mArtView.setImageBitmap(bitmap);
+ if (bitmap != null) {
+ mArtView.setVisibility(View.VISIBLE);
+ if (bitmap.getWidth() < bitmap.getHeight()) {
+ mArtView.setMaxHeight(getWindow().getDecorView().getWidth() * 9 / 16);
+ mArtView.setBackgroundColor(mBackgroundColor);
+ } else {
+ mArtView.setMaxHeight(Integer.MAX_VALUE);
+ }
+ } else {
+ mArtView.setVisibility(View.GONE);
+ }
}
}
}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
index d83ad2f..88a684c 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
@@ -17,6 +17,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
@@ -37,10 +38,13 @@
*/
public final class MediaRouteDescriptor {
private static final String KEY_ID = "id";
+ private static final String KEY_CHILD_IDS = "childIds";
private static final String KEY_NAME = "name";
private static final String KEY_DESCRIPTION = "status";
+ private static final String KEY_ICON_URI = "iconUri";
private static final String KEY_ENABLED = "enabled";
private static final String KEY_CONNECTING = "connecting";
+ private static final String KEY_CONNECTION_STATE = "connectionState";
private static final String KEY_CONTROL_FILTERS = "controlFilters";
private static final String KEY_PLAYBACK_TYPE = "playbackType";
private static final String KEY_PLAYBACK_STREAM = "playbackStream";
@@ -73,6 +77,19 @@
}
/**
+ * Gets the child ids of the route.
+ * <p>
+ * A route descriptor that has one or more child route ids represents a
+ * route group. A child route may belong to another group.
+ * </p>
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public List<String> getChildIds() {
+ return mBundle.getStringArrayList(KEY_CHILD_IDS);
+ }
+
+ /**
* Gets the user-visible name of the route.
* <p>
* The route name identifies the destination represented by the route.
@@ -95,6 +112,19 @@
}
/**
+ * Gets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p>
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public Uri getIconUri() {
+ String iconUri = mBundle.getString(KEY_ICON_URI);
+ return iconUri == null ? null : Uri.parse(iconUri);
+ }
+
+ /**
* Gets whether the route is enabled.
*/
public boolean isEnabled() {
@@ -103,16 +133,34 @@
/**
* Gets whether the route is connecting.
+ * STOPSHIP: Deprecate or keep.
*/
public boolean isConnecting() {
return mBundle.getBoolean(KEY_CONNECTING, false);
}
/**
- * Gets whether the route can be disconnected without stopping playback. To
- * specify that the route should disconnect without stopping use
+ * Gets the connection state of the route.
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public int getConnectionState() {
+ return mBundle.getInt(KEY_CONNECTION_STATE,
+ MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED);
+ }
+
+ /**
+ * Gets whether the route can be disconnected without stopping playback.
+ * <p>
+ * The route can normally be disconnected without stopping playback when
+ * the destination device on the route is connected to two or more source
+ * devices. The route provider should update the route immediately when the
+ * number of connected devices changes.
+ * </p><p>
+ * To specify that the route should disconnect without stopping use
* {@link MediaRouter#unselect(int)} with
* {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}.
+ * </p>
*/
public boolean canDisconnectAndKeepPlaying() {
return mBundle.getBoolean(KEY_CAN_DISCONNECT, false);
@@ -216,10 +264,13 @@
StringBuilder result = new StringBuilder();
result.append("MediaRouteDescriptor{ ");
result.append("id=").append(getId());
+ result.append(", childIds=").append(getChildIds());
result.append(", name=").append(getName());
result.append(", description=").append(getDescription());
+ result.append(", iconUri=").append(getIconUri());
result.append(", isEnabled=").append(isEnabled());
result.append(", isConnecting=").append(isConnecting());
+ result.append(", connectionState=").append(getConnectionState());
result.append(", controlFilters=").append(Arrays.toString(getControlFilters().toArray()));
result.append(", playbackType=").append(getPlaybackType());
result.append(", playbackStream=").append(getPlaybackStream());
@@ -257,6 +308,7 @@
*/
public static final class Builder {
private final Bundle mBundle;
+ private ArrayList<String> mChildIds;
private ArrayList<IntentFilter> mControlFilters;
/**
@@ -302,6 +354,51 @@
}
/**
+ * Adds a child id of the route.
+ * <p>
+ * A route descriptor that has one or more child route ids represents a
+ * route group. A child route may belong to another group.
+ * </p>
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public Builder addChildId(String childId) {
+ if (TextUtils.isEmpty(childId)) {
+ throw new IllegalArgumentException("childId must not be empty");
+ }
+
+ if (mChildIds == null) {
+ mChildIds = new ArrayList<>();
+ }
+ if (!mChildIds.contains(childId)) {
+ mChildIds.add(childId);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a list of child ids of the route.
+ * <p>
+ * A route descriptor that has one or more child route ids represents a
+ * route group. A child route may belong to another group.
+ * </p>
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public Builder addChildIds(Collection<String> childIds) {
+ if (childIds == null) {
+ throw new IllegalArgumentException("childIds must not be null");
+ }
+
+ if (!childIds.isEmpty()) {
+ for (String childId : childIds) {
+ addChildId(childId);
+ }
+ }
+ return this;
+ }
+
+ /**
* Sets the user-visible name of the route.
* <p>
* The route name identifies the destination represented by the route.
@@ -326,6 +423,31 @@
}
/**
+ * Sets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p><p>
+ * The URI must be one of the following formats:
+ * <ul>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+ * </li>
+ * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+ * </ul>
+ * </p>
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public Builder setIconUri(Uri iconUri) {
+ if (iconUri == null) {
+ throw new IllegalArgumentException("iconUri must not be null");
+ }
+ mBundle.putString(KEY_ICON_URI, iconUri.toString());
+ return this;
+ }
+
+
+ /**
* Sets whether the route is enabled.
* <p>
* Disabled routes represent routes that a route provider knows about, such as paired
@@ -340,6 +462,7 @@
/**
* Sets whether the route is in the process of connecting and is not yet
* ready for use.
+ * STOPSHIP: Deprecate or keep.
*/
public Builder setConnecting(boolean connecting) {
mBundle.putBoolean(KEY_CONNECTING, connecting);
@@ -347,6 +470,16 @@
}
/**
+ * Sets the route's connection state.
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public Builder setConnectionState(int connectionState) {
+ mBundle.putInt(KEY_CONNECTION_STATE, connectionState);
+ return this;
+ }
+
+ /**
* Sets whether the route can be disconnected without stopping playback.
*/
public Builder setCanDisconnect(boolean canDisconnect) {
@@ -461,6 +594,9 @@
if (mControlFilters != null) {
mBundle.putParcelableArrayList(KEY_CONTROL_FILTERS, mControlFilters);
}
+ if (mChildIds != null) {
+ mBundle.putStringArrayList(KEY_CHILD_IDS, mChildIds);
+ }
return new MediaRouteDescriptor(mBundle, mControlFilters);
}
}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index d37ed95..614dade 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -17,7 +17,6 @@
package android.support.v7.media;
import android.app.ActivityManager;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +25,7 @@
import android.content.IntentSender;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -37,6 +37,7 @@
import android.support.v4.hardware.display.DisplayManagerCompat;
import android.support.v4.media.VolumeProviderCompat;
import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.util.Pair;
import android.support.v7.media.MediaRouteProvider.ProviderMetadata;
import android.util.Log;
import android.view.Display;
@@ -46,8 +47,10 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
/**
* MediaRouter allows applications to control the routing of media channels
@@ -762,16 +765,18 @@
* route and the manner in which it is used and controlled.
* </p>
*/
- public static final class RouteInfo {
+ public static class RouteInfo {
private final ProviderInfo mProvider;
private final String mDescriptorId;
private final String mUniqueId;
private String mName;
private String mDescription;
+ private Uri mIconUri;
private boolean mEnabled;
private boolean mConnecting;
+ private int mConnectionState;
private boolean mCanDisconnect;
- private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>();
+ private final ArrayList<IntentFilter> mControlFilters = new ArrayList<>();
private int mPlaybackType;
private int mPlaybackStream;
private int mVolumeHandling;
@@ -781,7 +786,41 @@
private int mPresentationDisplayId = -1;
private Bundle mExtras;
private IntentSender mSettingsIntent;
- private MediaRouteDescriptor mDescriptor;
+ MediaRouteDescriptor mDescriptor;
+
+ /** @hide */
+ @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+ CONNECTION_STATE_CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface ConnectionState {}
+
+ /**
+ * The default connection state indicating the route is disconnected.
+ *
+ * @see #getConnectionState
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+ /**
+ * A connection state indicating the route is in the process of connecting and is not yet
+ * ready for use.
+ *
+ * @see #getConnectionState
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public static final int CONNECTION_STATE_CONNECTING = 1;
+
+ /**
+ * A connection state indicating the route is connected.
+ *
+ * @see #getConnectionState
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public static final int CONNECTION_STATE_CONNECTED = 2;
/** @hide */
@IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
@@ -889,6 +928,20 @@
}
/**
+ * Gets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p>
+ *
+ * @return The URI of the icon representing this route, or null if none.
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public Uri getIconUri() {
+ return mIconUri;
+ }
+
+ /**
* Returns true if this route is enabled and may be selected.
*
* @return True if this route is enabled.
@@ -902,12 +955,26 @@
* yet ready for use.
*
* @return True if this route is in the process of connecting.
+ * STOPSHIP: Deprecate or keep.
*/
public boolean isConnecting() {
return mConnecting;
}
/**
+ * Gets the connection state of the route.
+ *
+ * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ @ConnectionState
+ public int getConnectionState() {
+ return mConnectionState;
+ }
+
+ /**
* Returns true if this route is currently selected.
*
* @return True if this route is currently selected.
@@ -1253,8 +1320,10 @@
return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
+ ", name=" + mName
+ ", description=" + mDescription
+ + ", iconUri=" + mIconUri
+ ", enabled=" + mEnabled
+ ", connecting=" + mConnecting
+ + ", connectionState=" + mConnectionState
+ ", canDisconnect=" + mCanDisconnect
+ ", playbackType=" + mPlaybackType
+ ", playbackStream=" + mPlaybackStream
@@ -1268,69 +1337,83 @@
+ " }";
}
- int updateDescriptor(MediaRouteDescriptor descriptor) {
+ int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
int changes = 0;
if (mDescriptor != descriptor) {
- mDescriptor = descriptor;
- if (descriptor != null) {
- if (!equal(mName, descriptor.getName())) {
- mName = descriptor.getName();
- changes |= CHANGE_GENERAL;
- }
- if (!equal(mDescription, descriptor.getDescription())) {
- mDescription = descriptor.getDescription();
- changes |= CHANGE_GENERAL;
- }
- if (mEnabled != descriptor.isEnabled()) {
- mEnabled = descriptor.isEnabled();
- changes |= CHANGE_GENERAL;
- }
- if (mConnecting != descriptor.isConnecting()) {
- mConnecting = descriptor.isConnecting();
- changes |= CHANGE_GENERAL;
- }
- if (!mControlFilters.equals(descriptor.getControlFilters())) {
- mControlFilters.clear();
- mControlFilters.addAll(descriptor.getControlFilters());
- changes |= CHANGE_GENERAL;
- }
- if (mPlaybackType != descriptor.getPlaybackType()) {
- mPlaybackType = descriptor.getPlaybackType();
- changes |= CHANGE_GENERAL;
- }
- if (mPlaybackStream != descriptor.getPlaybackStream()) {
- mPlaybackStream = descriptor.getPlaybackStream();
- changes |= CHANGE_GENERAL;
- }
- if (mVolumeHandling != descriptor.getVolumeHandling()) {
- mVolumeHandling = descriptor.getVolumeHandling();
- changes |= CHANGE_GENERAL | CHANGE_VOLUME;
- }
- if (mVolume != descriptor.getVolume()) {
- mVolume = descriptor.getVolume();
- changes |= CHANGE_GENERAL | CHANGE_VOLUME;
- }
- if (mVolumeMax != descriptor.getVolumeMax()) {
- mVolumeMax = descriptor.getVolumeMax();
- changes |= CHANGE_GENERAL | CHANGE_VOLUME;
- }
- if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
- mPresentationDisplayId = descriptor.getPresentationDisplayId();
- mPresentationDisplay = null;
- changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
- }
- if (!equal(mExtras, descriptor.getExtras())) {
- mExtras = descriptor.getExtras();
- changes |= CHANGE_GENERAL;
- }
- if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
- mSettingsIntent = descriptor.getSettingsActivity();
- changes |= CHANGE_GENERAL;
- }
- if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
- mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
- changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
- }
+ changes = updateDescriptor(descriptor);
+ }
+ return changes;
+ }
+
+ int updateDescriptor(MediaRouteDescriptor descriptor) {
+ int changes = 0;
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ if (!equal(mName, descriptor.getName())) {
+ mName = descriptor.getName();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!equal(mDescription, descriptor.getDescription())) {
+ mDescription = descriptor.getDescription();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!equal(mIconUri, descriptor.getIconUri())) {
+ mIconUri = descriptor.getIconUri();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mEnabled != descriptor.isEnabled()) {
+ mEnabled = descriptor.isEnabled();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mConnecting != descriptor.isConnecting()) {
+ mConnecting = descriptor.isConnecting();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mConnectionState != descriptor.getConnectionState()) {
+ mConnectionState = descriptor.getConnectionState();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!mControlFilters.equals(descriptor.getControlFilters())) {
+ mControlFilters.clear();
+ mControlFilters.addAll(descriptor.getControlFilters());
+ changes |= CHANGE_GENERAL;
+ }
+ if (mPlaybackType != descriptor.getPlaybackType()) {
+ mPlaybackType = descriptor.getPlaybackType();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mPlaybackStream != descriptor.getPlaybackStream()) {
+ mPlaybackStream = descriptor.getPlaybackStream();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mVolumeHandling != descriptor.getVolumeHandling()) {
+ mVolumeHandling = descriptor.getVolumeHandling();
+ changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+ }
+ if (mVolume != descriptor.getVolume()) {
+ mVolume = descriptor.getVolume();
+ changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+ }
+ if (mVolumeMax != descriptor.getVolumeMax()) {
+ mVolumeMax = descriptor.getVolumeMax();
+ changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+ }
+ if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
+ mPresentationDisplayId = descriptor.getPresentationDisplayId();
+ mPresentationDisplay = null;
+ changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+ }
+ if (!equal(mExtras, descriptor.getExtras())) {
+ mExtras = descriptor.getExtras();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
+ mSettingsIntent = descriptor.getSettingsActivity();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
+ mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
+ changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
}
}
return changes;
@@ -1340,12 +1423,92 @@
return mDescriptorId;
}
- MediaRouteProvider getProviderInstance() {
+ /** @hide */
+ public MediaRouteProvider getProviderInstance() {
return mProvider.getProviderInstance();
}
}
/**
+ * Information about a route that consists of multiple other routes in a group.
+ * @hide
+ * STOPSHIP: Unhide or remove.
+ */
+ public static class RouteGroup extends RouteInfo {
+ private List<RouteInfo> mRoutes = new ArrayList<>();
+
+ RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId) {
+ super(provider, descriptorId, uniqueId);
+ }
+
+ /**
+ * @return The number of routes in this group
+ */
+ public int getRouteCount() {
+ return mRoutes.size();
+ }
+
+ /**
+ * Returns the route in this group at the specified index
+ *
+ * @param index Index to fetch
+ * @return The route at index
+ */
+ public RouteInfo getRouteAt(int index) {
+ return mRoutes.get(index);
+ }
+
+ /**
+ * Returns the routes in this group
+ *
+ * @return The list of the routes in this group
+ */
+ public List<RouteInfo> getRoutes() {
+ return mRoutes;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(super.toString());
+ sb.append('[');
+ final int count = mRoutes.size();
+ for (int i = 0; i < count; i++) {
+ if (i > 0) sb.append(", ");
+ sb.append(mRoutes.get(i));
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ @Override
+ int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ List<String> childIds = descriptor.getChildIds();
+ List<RouteInfo> routes = new ArrayList<>();
+ changed = childIds.size() != mRoutes.size();
+ for (String childId : childIds) {
+ String uniqueChildId = sGlobal.getUniqueId(getProvider(), childId);
+ RouteInfo child = sGlobal.getRoute(uniqueChildId);
+ if (child != null) {
+ routes.add(child);
+ if (!changed && !mRoutes.contains(child)) {
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ mRoutes = routes;
+ }
+ }
+ }
+ return (changed ? CHANGE_GENERAL : 0) | super.updateDescriptor(descriptor);
+ }
+ }
+
+ /**
* Provides information about a media route provider.
* <p>
* This object may be used to determine which media route provider has
@@ -1354,7 +1517,7 @@
*/
public static final class ProviderInfo {
private final MediaRouteProvider mProviderInstance;
- private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+ private final List<RouteInfo> mRoutes = new ArrayList<>();
private final ProviderMetadata mMetadata;
private MediaRouteProviderDescriptor mDescriptor;
@@ -1602,13 +1765,12 @@
implements SystemMediaRouteProvider.SyncCallback,
RegisteredMediaRouteProviderWatcher.Callback {
private final Context mApplicationContext;
- private final ArrayList<WeakReference<MediaRouter>> mRouters =
- new ArrayList<WeakReference<MediaRouter>>();
- private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
- private final ArrayList<ProviderInfo> mProviders =
- new ArrayList<ProviderInfo>();
+ private final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
+ private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
+ private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
+ private final ArrayList<ProviderInfo> mProviders = new ArrayList<>();
private final ArrayList<RemoteControlClientRecord> mRemoteControlClients =
- new ArrayList<RemoteControlClientRecord>();
+ new ArrayList<>();
private final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo =
new RemoteControlClientCompat.PlaybackInfo();
private final ProviderCallback mProviderCallback = new ProviderCallback();
@@ -1720,6 +1882,15 @@
}
}
+ public RouteInfo getRoute(String uniqueId) {
+ for (RouteInfo info : mRoutes) {
+ if (info.mUniqueId.equals(uniqueId)) {
+ return info;
+ }
+ }
+ return null;
+ }
+
public List<RouteInfo> getRoutes() {
return mRoutes;
}
@@ -1930,6 +2101,11 @@
final List<MediaRouteDescriptor> routeDescriptors =
providerDescriptor.getRoutes();
final int routeCount = routeDescriptors.size();
+ // Updating route group's contents requires all member routes' information.
+ // Add the groups to the lists and update them later.
+ List<Pair<RouteInfo, MediaRouteDescriptor>> addedGroups = new ArrayList<>();
+ List<Pair<RouteInfo, MediaRouteDescriptor>> updatedGroups =
+ new ArrayList<>();
for (int i = 0; i < routeCount; i++) {
final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
final String id = routeDescriptor.getId();
@@ -1937,16 +2113,23 @@
if (sourceIndex < 0) {
// 1. Add the route to the list.
String uniqueId = assignRouteUniqueId(provider, id);
- RouteInfo route = new RouteInfo(provider, id, uniqueId);
+ boolean isGroup = routeDescriptor.getChildIds() != null;
+ RouteInfo route = isGroup ? new RouteGroup(provider, id, uniqueId) :
+ new RouteInfo(provider, id, uniqueId);
provider.mRoutes.add(targetIndex++, route);
mRoutes.add(route);
// 2. Create the route's contents.
- route.updateDescriptor(routeDescriptor);
- // 3. Notify clients about addition.
- if (DEBUG) {
- Log.d(TAG, "Route added: " + route);
+ if (isGroup) {
+ addedGroups.add(new Pair(route, routeDescriptor));
+ } else {
+ route.maybeUpdateDescriptor(routeDescriptor);
+ // 3. Notify clients about addition.
+ if (DEBUG) {
+ Log.d(TAG, "Route added: " + route);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
}
- mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
+
} else if (sourceIndex < targetIndex) {
Log.w(TAG, "Ignoring route descriptor with duplicate id: "
+ routeDescriptor);
@@ -1956,34 +2139,33 @@
Collections.swap(provider.mRoutes,
sourceIndex, targetIndex++);
// 2. Update the route's contents.
- int changes = route.updateDescriptor(routeDescriptor);
- // 3. Notify clients about changes.
- if (changes != 0) {
- if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
- if (DEBUG) {
- Log.d(TAG, "Route changed: " + route);
+ if (route instanceof RouteGroup) {
+ updatedGroups.add(new Pair(route, routeDescriptor));
+ } else {
+ // 3. Notify clients about changes.
+ if (updateRouteDescriptorAndNotify(route, routeDescriptor)
+ != 0) {
+ if (route == mSelectedRoute) {
+ selectedRouteDescriptorChanged = true;
}
- mCallbackHandler.post(
- CallbackHandler.MSG_ROUTE_CHANGED, route);
}
- if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
- if (DEBUG) {
- Log.d(TAG, "Route volume changed: " + route);
- }
- mCallbackHandler.post(
- CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
- }
- if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
- if (DEBUG) {
- Log.d(TAG, "Route presentation display changed: "
- + route);
- }
- mCallbackHandler.post(CallbackHandler.
- MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
- }
- if (route == mSelectedRoute) {
- selectedRouteDescriptorChanged = true;
- }
+ }
+ }
+ }
+ // Update the new and/or existing groups.
+ for (Pair<RouteInfo, MediaRouteDescriptor> pair : addedGroups) {
+ RouteInfo route = pair.first;
+ route.maybeUpdateDescriptor(pair.second);
+ if (DEBUG) {
+ Log.d(TAG, "Route added: " + route);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
+ }
+ for (Pair<RouteInfo, MediaRouteDescriptor> pair : updatedGroups) {
+ RouteInfo route = pair.first;
+ if (updateRouteDescriptorAndNotify(route, pair.second) != 0) {
+ if (route == mSelectedRoute) {
+ selectedRouteDescriptorChanged = true;
}
}
}
@@ -1996,7 +2178,7 @@
for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
// 1. Delete the route's contents.
RouteInfo route = provider.mRoutes.get(i);
- route.updateDescriptor(null);
+ route.maybeUpdateDescriptor(null);
// 2. Remove the route from the list.
mRoutes.remove(route);
}
@@ -2025,18 +2207,52 @@
}
}
+ private int updateRouteDescriptorAndNotify(RouteInfo route,
+ MediaRouteDescriptor routeDescriptor) {
+ int changes = route.maybeUpdateDescriptor(routeDescriptor);
+ if (changes != 0) {
+ if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Route changed: " + route);
+ }
+ mCallbackHandler.post(
+ CallbackHandler.MSG_ROUTE_CHANGED, route);
+ }
+ if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Route volume changed: " + route);
+ }
+ mCallbackHandler.post(
+ CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
+ }
+ if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Route presentation display changed: "
+ + route);
+ }
+ mCallbackHandler.post(CallbackHandler.
+ MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
+ }
+ }
+ return changes;
+ }
+
private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
// Although route descriptor ids are unique within a provider, it's
// possible for there to be two providers with the same package name.
// Therefore we must dedupe the composite id.
- String uniqueId = provider.getComponentName().flattenToShortString()
- + ":" + routeDescriptorId;
+ String componentName = provider.getComponentName().flattenToShortString();
+ String uniqueId = componentName + ":" + routeDescriptorId;
if (findRouteByUniqueId(uniqueId) < 0) {
+ mUniqueIdMap.put(new Pair(componentName, routeDescriptorId), uniqueId);
return uniqueId;
}
+ Log.w(TAG, "Either " + routeDescriptorId + " isn't unique in " + componentName
+ + " or we're trying to assign a unique ID for an already added route");
for (int i = 2; ; i++) {
String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
if (findRouteByUniqueId(newUniqueId) < 0) {
+ mUniqueIdMap.put(new Pair(componentName, routeDescriptorId), newUniqueId);
return newUniqueId;
}
}
@@ -2052,6 +2268,11 @@
return -1;
}
+ private String getUniqueId(ProviderInfo provider, String routeDescriptorId) {
+ String componentName = provider.getComponentName().flattenToShortString();
+ return mUniqueIdMap.get(new Pair(componentName, routeDescriptorId));
+ }
+
private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) {
// Update default route.
if (mDefaultRoute != null && !isRouteSelectable(mDefaultRoute)) {
diff --git a/v7/palette/api/current.txt b/v7/palette/api/current.txt
index 1b6c745..fac6a55 100644
--- a/v7/palette/api/current.txt
+++ b/v7/palette/api/current.txt
@@ -27,10 +27,12 @@
ctor public Palette.Builder(java.util.List<android.support.v7.graphics.Palette.Swatch>);
method public android.support.v7.graphics.Palette.Builder addFilter(android.support.v7.graphics.Palette.Filter);
method public android.support.v7.graphics.Palette.Builder clearFilters();
+ method public android.support.v7.graphics.Palette.Builder clearRegion();
method public android.support.v7.graphics.Palette generate();
method public android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generate(android.support.v7.graphics.Palette.PaletteAsyncListener);
method public android.support.v7.graphics.Palette.Builder maximumColorCount(int);
method public android.support.v7.graphics.Palette.Builder resizeBitmapSize(int);
+ method public android.support.v7.graphics.Palette.Builder setRegion(int, int, int, int);
}
public static abstract interface Palette.Filter {
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 1ffb7a1..a476559 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
@@ -18,6 +18,7 @@
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.Rect;
import android.os.AsyncTask;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
@@ -441,11 +442,12 @@
* Builder class for generating {@link Palette} instances.
*/
public static final class Builder {
- private List<Swatch> mSwatches;
- private Bitmap mBitmap;
+ private final List<Swatch> mSwatches;
+ private final Bitmap mBitmap;
private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION;
private final List<Filter> mFilters = new ArrayList<>();
+ private Rect mRegion;
private Generator mGenerator;
@@ -453,11 +455,12 @@
* Construct a new {@link Builder} using a source {@link Bitmap}
*/
public Builder(Bitmap bitmap) {
- this();
if (bitmap == null || bitmap.isRecycled()) {
throw new IllegalArgumentException("Bitmap is not valid");
}
+ mFilters.add(DEFAULT_FILTER);
mBitmap = bitmap;
+ mSwatches = null;
}
/**
@@ -465,15 +468,12 @@
* Typically only used for testing.
*/
public Builder(List<Swatch> swatches) {
- this();
if (swatches == null || swatches.isEmpty()) {
throw new IllegalArgumentException("List of Swatches is not valid");
}
- mSwatches = swatches;
- }
-
- private Builder() {
mFilters.add(DEFAULT_FILTER);
+ mSwatches = swatches;
+ mBitmap = null;
}
/**
@@ -536,6 +536,37 @@
}
/**
+ * Set a region of the bitmap to be used exclusively when calculating the palette.
+ * <p>This only works when the original input is a {@link Bitmap}.</p>
+ *
+ * @param left The left side of the rectangle used for the region.
+ * @param top The top of the rectangle used for the region.
+ * @param right The right side of the rectangle used for the region.
+ * @param bottom The bottom of the rectangle used for the region.
+ */
+ public Builder setRegion(int left, int top, int right, int bottom) {
+ if (mBitmap != null) {
+ if (mRegion == null) mRegion = new Rect();
+ // Set the Rect to be initially the whole Bitmap
+ mRegion.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ // Now just get the intersection with the region
+ if (!mRegion.intersect(left, top, right, bottom)) {
+ throw new IllegalArgumentException("The given region must intersect with "
+ + "the Bitmap's dimensions.");
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
+ */
+ public Builder clearRegion() {
+ mRegion = null;
+ return this;
+ }
+
+ /**
* Generate and return the {@link Palette} synchronously.
*/
public Palette generate() {
@@ -554,24 +585,32 @@
}
// First we'll scale down the bitmap so it's largest dimension is as specified
- final Bitmap scaledBitmap = scaleBitmapDown(mBitmap, mResizeMaxDimension);
+ final Bitmap bitmap = scaleBitmapDown(mBitmap, mResizeMaxDimension);
if (logger != null) {
logger.addSplit("Processed Bitmap");
}
- // Now generate a quantizer from the Bitmap
- final int width = scaledBitmap.getWidth();
- final int height = scaledBitmap.getHeight();
- final int[] pixels = new int[width * height];
- scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+ final Rect region = mRegion;
+ if (bitmap != mBitmap && region != null) {
+ // If we have a scaled bitmap and a selected region, we need to scale down the
+ // region to match the new scale
+ final float scale = bitmap.getWidth() / (float) mBitmap.getWidth();
+ region.left = (int) Math.floor(region.left * scale);
+ region.top = (int) Math.floor(region.top * scale);
+ region.right = (int) Math.ceil(region.right * scale);
+ region.bottom = (int) Math.ceil(region.bottom * scale);
+ }
- final ColorCutQuantizer quantizer = new ColorCutQuantizer(pixels, mMaxColors,
+ // Now generate a quantizer from the Bitmap
+ final ColorCutQuantizer quantizer = new ColorCutQuantizer(
+ getPixelsFromBitmap(),
+ mMaxColors,
mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()]));
// If created a new bitmap, recycle it
- if (scaledBitmap != mBitmap) {
- scaledBitmap.recycle();
+ if (bitmap != mBitmap) {
+ bitmap.recycle();
}
swatches = quantizer.getQuantizedColors();
@@ -629,6 +668,35 @@
}
}, mBitmap);
}
+
+ private int[] getPixelsFromBitmap() {
+ final Bitmap bitmap = mBitmap;
+ final int bitmapWidth = bitmap.getWidth();
+ final int bitmapHeight = bitmap.getHeight();
+ final int[] pixels = new int[bitmapWidth * bitmapHeight];
+
+ if (mRegion == null) {
+ // If we don't have a region, return all of the pixels
+ bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
+ return pixels;
+ } else {
+ // If we do have a region, lets create a subset array containing only the region's
+ // pixels
+ final int regionWidth = mRegion.width();
+ final int regionHeight = mRegion.height();
+ // First read the pixels within the region
+ bitmap.getPixels(pixels, 0, bitmapWidth, mRegion.left, mRegion.top,
+ regionWidth, regionHeight);
+ // pixels now contains all of the pixels, but not packed together. We need to
+ // iterate through each row and copy them into a new smaller array
+ final int[] subsetPixels = new int[regionWidth * mRegion.height()];
+ for (int row = mRegion.top; row < mRegion.bottom; row++) {
+ System.arraycopy(pixels, (row * bitmapWidth) + mRegion.left,
+ subsetPixels, row * regionWidth, regionWidth);
+ }
+ return subsetPixels;
+ }
+ }
}
static abstract class Generator {
diff --git a/v7/preference/res/layout/preference_information.xml b/v7/preference/res/layout/preference_information.xml
index e3be3820..5815c46 100644
--- a/v7/preference/res/layout/preference_information.xml
+++ b/v7/preference/res/layout/preference_information.xml
@@ -33,14 +33,14 @@
android:layout_marginBottom="6sp"
android:layout_weight="1">
- <TextView android:id="@+android:id/title"
+ <TextView android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorSecondary" />
- <TextView android:id="@+android:id/summary"
+ <TextView android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
@@ -52,7 +52,7 @@
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
- <LinearLayout android:id="@+android:id/widget_frame"
+ <LinearLayout android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"