/*
* 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.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* An overview {@link Row} for a details fragment. This row consists of an image, a
* description view, and optionally a series of {@link Action}s that can be taken for
* the item.
*
* <h3>Actions</h3>
* Application uses {@link #setActionsAdapter(ObjectAdapter)} to set actions on the overview
* row. {@link SparseArrayObjectAdapter} is recommended for easily updating actions while
* maintaining the order. The application can add or remove actions on the UI thread after the
* row is bound to a view.
*
* <h3>Updating main item</h3>
* After the row is bound to a view, the application may call {@link #setItem(Object)}
* on UI thread and the view will be updated.
*
* <h3>Updating image</h3>
* After the row is bound to view, the application may change the image by calling {@link
* #setImageBitmap(Context, Bitmap)} or {@link #setImageDrawable(Drawable)} on the UI thread,
* and the view will be updated.
*/
public class DetailsOverviewRow extends Row {
/**
* Listener for changes of DetailsOverviewRow.
*/
public static class Listener {
/**
* Called when DetailsOverviewRow has changed image drawable.
*/
public void onImageDrawableChanged(DetailsOverviewRow row) {
}
/**
* Called when DetailsOverviewRow has changed main item.
*/
public void onItemChanged(DetailsOverviewRow row) {
}
/**
* Called when DetailsOverviewRow has changed actions adapter.
*/
public void onActionsAdapterChanged(DetailsOverviewRow row) {
}
}
private Object mItem;
private Drawable mImageDrawable;
private boolean mImageScaleUpAllowed = true;
private ArrayList<WeakReference<Listener>> mListeners;
private PresenterSelector mDefaultActionPresenter = new ActionPresenterSelector();
private ObjectAdapter mActionsAdapter = new ArrayObjectAdapter(mDefaultActionPresenter);
/**
* Constructor for a DetailsOverviewRow.
*
* @param item The main item for the details page.
*/
public DetailsOverviewRow(Object item) {
super(null);
mItem = item;
verify();
}
/**
* Adds listener for the details page.
*/
final void addListener(Listener listener) {
if (mListeners == null) {
mListeners = new ArrayList<WeakReference<Listener>>();
} else {
for (int i = 0; i < mListeners.size();) {
Listener l = mListeners.get(i).get();
if (l == null) {
mListeners.remove(i);
} else {
if (l == listener) {
return;
}
i++;
}
}
}
mListeners.add(new WeakReference<Listener>(listener));
}
/**
* Removes listener of the details page.
*/
final void removeListener(Listener listener) {
if (mListeners != null) {
for (int i = 0; i < mListeners.size();) {
Listener l = mListeners.get(i).get();
if (l == null) {
mListeners.remove(i);
} else {
if (l == listener) {
mListeners.remove(i);
return;
}
i++;
}
}
}
}
/**
* Notifies listeners for main item change on UI thread.
*/
final void notifyItemChanged() {
if (mListeners != null) {
for (int i = 0; i < mListeners.size();) {
Listener l = mListeners.get(i).get();
if (l == null) {
mListeners.remove(i);
} else {
l.onItemChanged(this);
i++;
}
}
}
}
/**
* Notifies listeners for image related change on UI thread.
*/
final void notifyImageDrawableChanged() {
if (mListeners != null) {
for (int i = 0; i < mListeners.size();) {
Listener l = mListeners.get(i).get();
if (l == null) {
mListeners.remove(i);
} else {
l.onImageDrawableChanged(this);
i++;
}
}
}
}
/**
* Notifies listeners for actions adapter changed on UI thread.
*/
final void notifyActionsAdapterChanged() {
if (mListeners != null) {
for (int i = 0; i < mListeners.size();) {
Listener l = mListeners.get(i).get();
if (l == null) {
mListeners.remove(i);
} else {
l.onActionsAdapterChanged(this);
i++;
}
}
}
}
/**
* Returns the main item for the details page.
*/
public final Object getItem() {
return mItem;
}
/**
* Sets the main item for the details page. Must be called on UI thread after
* row is bound to view.
*/
public final void setItem(Object item) {
if (item != mItem) {
mItem = item;
notifyItemChanged();
}
}
/**
* Sets a drawable as the image of this details overview. Must be called on UI thread
* after row is bound to view.
*
* @param drawable The drawable to set.
*/
public final void setImageDrawable(Drawable drawable) {
if (mImageDrawable != drawable) {
mImageDrawable = drawable;
notifyImageDrawableChanged();
}
}
/**
* Sets a Bitmap as the image of this details overview. Must be called on UI thread
* after row is bound to view.
*
* @param context The context to retrieve display metrics from.
* @param bm The bitmap to set.
*/
public final void setImageBitmap(Context context, Bitmap bm) {
mImageDrawable = new BitmapDrawable(context.getResources(), bm);
notifyImageDrawableChanged();
}
/**
* Returns the image drawable of this details overview.
*
* @return The overview's image drawable, or null if no drawable has been
* assigned.
*/
public final Drawable getImageDrawable() {
return mImageDrawable;
}
/**
* Allows or disallows scaling up of images.
* Images will always be scaled down if necessary. Must be called on UI thread
* after row is bound to view.
*/
public void setImageScaleUpAllowed(boolean allowed) {
if (allowed != mImageScaleUpAllowed) {
mImageScaleUpAllowed = allowed;
notifyImageDrawableChanged();
}
}
/**
* Returns true if the image may be scaled up; false otherwise.
*/
public boolean isImageScaleUpAllowed() {
return mImageScaleUpAllowed;
}
/**
* Returns the actions adapter. Throws ClassCastException if the current
* actions adapter is not an instance of {@link ArrayObjectAdapter}.
*/
private ArrayObjectAdapter getArrayObjectAdapter() {
return (ArrayObjectAdapter) mActionsAdapter;
}
/**
* Adds an Action to the overview. It will throw ClassCastException if the current actions
* adapter is not an instance of {@link ArrayObjectAdapter}. Must be called on the UI thread.
*
* @param action The Action to add.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
public final void addAction(Action action) {
getArrayObjectAdapter().add(action);
}
/**
* Adds an Action to the overview at the specified position. It will throw ClassCastException if
* current actions adapter is not an instance of f{@link ArrayObjectAdapter}. Must be called
* on the UI thread.
*
* @param pos The position to insert the Action.
* @param action The Action to add.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
public final void addAction(int pos, Action action) {
getArrayObjectAdapter().add(pos, action);
}
/**
* Removes the given Action from the overview. It will throw ClassCastException if current
* actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread.
*
* @param action The Action to remove.
* @return true if the overview contained the specified Action.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
public final boolean removeAction(Action action) {
return getArrayObjectAdapter().remove(action);
}
/**
* Returns a read-only view of the list of Actions of this details overview. It will throw
* ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be
* called on UI thread.
*
* @return An unmodifiable view of the list of Actions.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
public final List<Action> getActions() {
return getArrayObjectAdapter().unmodifiableList();
}
/**
* Returns the {@link ObjectAdapter} for actions.
*/
public final ObjectAdapter getActionsAdapter() {
return mActionsAdapter;
}
/**
* Sets the {@link ObjectAdapter} for actions. A default {@link PresenterSelector} will be
* attached to the adapter if it doesn't have one.
*
* @param adapter Adapter for actions.
*/
public final void setActionsAdapter(ObjectAdapter adapter) {
if (adapter != mActionsAdapter) {
mActionsAdapter = adapter;
if (mActionsAdapter.getPresenterSelector() == null) {
mActionsAdapter.setPresenterSelector(mDefaultActionPresenter);
}
notifyActionsAdapterChanged();
}
}
/**
* Returns the Action associated with the given keycode, or null if no associated action exists.
*/
public Action getActionForKeyCode(int keyCode) {
ObjectAdapter adapter = getActionsAdapter();
if (adapter != null) {
for (int i = 0; i < adapter.size(); i++) {
Action action = (Action) adapter.get(i);
if (action.respondsToKeyCode(keyCode)) {
return action;
}
}
}
return null;
}
private void verify() {
if (mItem == null) {
throw new IllegalArgumentException("Object cannot be null");
}
}
}