Merge "Add long/double read support."
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index 6839c8e..dffa969 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -16,10 +16,7 @@
package android.app;
-import com.android.internal.app.MediaRouteChooserDialogFragment;
-
import android.content.Context;
-import android.content.ContextWrapper;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.util.Log;
@@ -30,22 +27,38 @@
import java.lang.ref.WeakReference;
+/**
+ * The media route action provider displays a {@link MediaRouteButton media route button}
+ * in the application's {@link ActionBar} to allow the user to select routes and
+ * to control the currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying the route types with the {@link #setRouteTypes} method.
+ * </p><p>
+ * Refer to {@link MediaRouteButton} for a description of the button that will
+ * appear in the action bar menu. Note that instead of disabling the button
+ * when no routes are available, the action provider will instead make the
+ * menu item invisible. In this way, the button will only be visible when it
+ * is possible for the user to discover and select a matching route.
+ * </p>
+ */
public class MediaRouteActionProvider extends ActionProvider {
private static final String TAG = "MediaRouteActionProvider";
- private Context mContext;
- private MediaRouter mRouter;
- private MenuItem mMenuItem;
- private MediaRouteButton mView;
+ private final Context mContext;
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+
private int mRouteTypes;
+ private MediaRouteButton mButton;
private View.OnClickListener mExtendedSettingsListener;
- private RouterCallback mCallback;
public MediaRouteActionProvider(Context context) {
super(context);
+
mContext = context;
mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- mCallback = new RouterCallback(this);
+ mCallback = new MediaRouterCallback(this);
// Start with live audio by default.
// TODO Update this when new route types are added; segment by API level
@@ -53,80 +66,74 @@
setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
}
+ /**
+ * Sets the types of routes that will be shown in the media route chooser dialog
+ * launched by this button.
+ *
+ * @param types The route types to match.
+ */
public void setRouteTypes(int types) {
- if (mRouteTypes == types) return;
- if (mRouteTypes != 0) {
- mRouter.removeCallback(mCallback);
+ if (mRouteTypes != types) {
+ // FIXME: We currently have no way of knowing whether the action provider
+ // is still needed by the UI. Unfortunately this means the action provider
+ // may leak callbacks until garbage collection occurs. This may result in
+ // media route providers doing more work than necessary in the short term
+ // while trying to discover routes that are no longer of interest to the
+ // application. To solve this problem, the action provider will need some
+ // indication from the framework that it is being destroyed.
+ if (mRouteTypes != 0) {
+ mRouter.removeCallback(mCallback);
+ }
+ mRouteTypes = types;
+ if (types != 0) {
+ mRouter.addCallback(types, mCallback,
+ MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+ }
+ refreshRoute();
+
+ if (mButton != null) {
+ mButton.setRouteTypes(mRouteTypes);
+ }
}
- mRouteTypes = types;
- if (types != 0) {
- mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
- }
- if (mView != null) {
- mView.setRouteTypes(mRouteTypes);
+ }
+
+ public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+ mExtendedSettingsListener = listener;
+ if (mButton != null) {
+ mButton.setExtendedSettingsClickListener(listener);
}
}
@Override
+ @SuppressWarnings("deprecation")
public View onCreateActionView() {
throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead.");
}
@Override
public View onCreateActionView(MenuItem item) {
- if (mMenuItem != null || mView != null) {
+ if (mButton != null) {
Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
"with a menu item. Don't reuse MediaRouteActionProvider instances! " +
"Abandoning the old one...");
}
- mMenuItem = item;
- mView = new MediaRouteButton(mContext);
- mView.setCheatSheetEnabled(true);
- mView.setRouteTypes(mRouteTypes);
- mView.setExtendedSettingsClickListener(mExtendedSettingsListener);
- mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+
+ mButton = new MediaRouteButton(mContext);
+ mButton.setCheatSheetEnabled(true);
+ mButton.setRouteTypes(mRouteTypes);
+ mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
+ mButton.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
- return mView;
+ return mButton;
}
@Override
public boolean onPerformDefaultAction() {
- final FragmentManager fm = getActivity().getFragmentManager();
- // See if one is already attached to this activity.
- MediaRouteChooserDialogFragment dialogFragment =
- (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
- MediaRouteChooserDialogFragment.FRAGMENT_TAG);
- if (dialogFragment != null) {
- Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!");
- return false;
+ if (mButton != null) {
+ return mButton.showDialogInternal();
}
-
- dialogFragment = new MediaRouteChooserDialogFragment();
- dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener);
- dialogFragment.setRouteTypes(mRouteTypes);
- dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
- return true;
- }
-
- private Activity getActivity() {
- // Gross way of unwrapping the Activity so we can get the FragmentManager
- Context context = mContext;
- while (context instanceof ContextWrapper && !(context instanceof Activity)) {
- context = ((ContextWrapper) context).getBaseContext();
- }
- if (!(context instanceof Activity)) {
- throw new IllegalStateException("The MediaRouteActionProvider's Context " +
- "is not an Activity.");
- }
-
- return (Activity) context;
- }
-
- public void setExtendedSettingsClickListener(View.OnClickListener listener) {
- mExtendedSettingsListener = listener;
- if (mView != null) {
- mView.setExtendedSettingsClickListener(listener);
- }
+ return false;
}
@Override
@@ -136,36 +143,43 @@
@Override
public boolean isVisible() {
- return mRouter.getRouteCount() > 1;
+ return mRouter.isRouteAvailable(mRouteTypes,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
}
- private static class RouterCallback extends MediaRouter.SimpleCallback {
- private WeakReference<MediaRouteActionProvider> mAp;
+ private void refreshRoute() {
+ refreshVisibility();
+ }
- RouterCallback(MediaRouteActionProvider ap) {
- mAp = new WeakReference<MediaRouteActionProvider>(ap);
+ private static class MediaRouterCallback extends MediaRouter.SimpleCallback {
+ private final WeakReference<MediaRouteActionProvider> mProviderWeak;
+
+ public MediaRouterCallback(MediaRouteActionProvider provider) {
+ mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
}
@Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
- final MediaRouteActionProvider ap = mAp.get();
- if (ap == null) {
- router.removeCallback(this);
- return;
- }
-
- ap.refreshVisibility();
+ refreshRoute(router);
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo info) {
- final MediaRouteActionProvider ap = mAp.get();
- if (ap == null) {
- router.removeCallback(this);
- return;
- }
+ refreshRoute(router);
+ }
- ap.refreshVisibility();
+ @Override
+ public void onRouteChanged(MediaRouter router, RouteInfo info) {
+ refreshRoute(router);
+ }
+
+ private void refreshRoute(MediaRouter router) {
+ MediaRouteActionProvider provider = mProviderWeak.get();
+ if (provider != null) {
+ provider.refreshRoute();
+ } else {
+ router.removeCallback(this);
+ }
}
}
}
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index e75dc29..fa2813e 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -17,7 +17,7 @@
package android.app;
import com.android.internal.R;
-import com.android.internal.app.MediaRouteChooserDialogFragment;
+import com.android.internal.app.MediaRouteDialogPresenter;
import android.content.Context;
import android.content.ContextWrapper;
@@ -30,7 +30,6 @@
import android.media.MediaRouter.RouteInfo;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
@@ -38,17 +37,15 @@
import android.widget.Toast;
public class MediaRouteButton extends View {
- private static final String TAG = "MediaRouteButton";
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
- private MediaRouter mRouter;
- private final MediaRouteCallback mRouterCallback = new MediaRouteCallback();
private int mRouteTypes;
private boolean mAttachedToWindow;
private Drawable mRemoteIndicator;
private boolean mRemoteActive;
- private boolean mToggleMode;
private boolean mCheatSheetEnabled;
private boolean mIsConnecting;
@@ -56,12 +53,13 @@
private int mMinHeight;
private OnClickListener mExtendedSettingsClickListener;
- private MediaRouteChooserDialogFragment mDialogFragment;
+ // The checked state is used when connected to a remote route.
private static final int[] CHECKED_STATE_SET = {
R.attr.state_checked
};
+ // The activated state is used while connecting to a remote route.
private static final int[] ACTIVATED_STATE_SET = {
R.attr.state_activated
};
@@ -83,6 +81,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ mCallback = new MediaRouterCallback();
final TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, defStyleRes);
@@ -103,19 +102,87 @@
setRouteTypes(routeTypes);
}
- private void setRemoteIndicatorDrawable(Drawable d) {
- if (mRemoteIndicator != null) {
- mRemoteIndicator.setCallback(null);
- unscheduleDrawable(mRemoteIndicator);
+ /**
+ * Gets the media route types for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @return The route types.
+ */
+ public int getRouteTypes() {
+ return mRouteTypes;
+ }
+
+ /**
+ * Sets the types of routes that will be shown in the media route chooser dialog
+ * launched by this button.
+ *
+ * @param types The route types to match.
+ */
+ public void setRouteTypes(int types) {
+ if (mRouteTypes != types) {
+ if (mAttachedToWindow && mRouteTypes != 0) {
+ mRouter.removeCallback(mCallback);
+ }
+
+ mRouteTypes = types;
+
+ if (mAttachedToWindow && types != 0) {
+ mRouter.addCallback(types, mCallback,
+ MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+ }
+
+ refreshRoute();
}
- mRemoteIndicator = d;
- if (d != null) {
- d.setCallback(this);
- d.setState(getDrawableState());
- d.setVisible(getVisibility() == VISIBLE, false);
+ }
+
+ public void setExtendedSettingsClickListener(OnClickListener listener) {
+ mExtendedSettingsClickListener = listener;
+ }
+
+ /**
+ * Show the route chooser or controller dialog.
+ * <p>
+ * If the default route is selected or if the currently selected route does
+ * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog.
+ * Otherwise, shows the route controller dialog to offer the user
+ * a choice to disconnect from the route or perform other control actions
+ * such as setting the route's volume.
+ * </p><p>
+ * This will attach a {@link DialogFragment} to the containing Activity.
+ * </p>
+ */
+ public void showDialog() {
+ showDialogInternal();
+ }
+
+ boolean showDialogInternal() {
+ if (!mAttachedToWindow) {
+ return false;
}
- refreshDrawableState();
+ DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(),
+ mRouteTypes, mExtendedSettingsClickListener);
+ return f != null;
+ }
+
+ private Activity getActivity() {
+ // Gross way of unwrapping the Activity so we can get the FragmentManager
+ Context context = getContext();
+ while (context instanceof ContextWrapper) {
+ if (context instanceof Activity) {
+ return (Activity)context;
+ }
+ context = ((ContextWrapper)context).getBaseContext();
+ }
+ throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
+ }
+
+ /**
+ * Sets whether to enable showing a toast with the content descriptor of the
+ * button when the button is long pressed.
+ */
+ void setCheatSheetEnabled(boolean enable) {
+ mCheatSheetEnabled = enable;
}
@Override
@@ -125,29 +192,7 @@
if (!handled) {
playSoundEffect(SoundEffectConstants.CLICK);
}
-
- if (mToggleMode) {
- if (mRemoteActive) {
- mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute(), true);
- } else {
- final int N = mRouter.getRouteCount();
- for (int i = 0; i < N; i++) {
- final RouteInfo route = mRouter.getRouteAt(i);
- if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
- route != mRouter.getDefaultRoute()) {
- mRouter.selectRouteInt(mRouteTypes, route, true);
- }
- }
- }
- } else {
- showDialog();
- }
-
- return handled;
- }
-
- void setCheatSheetEnabled(boolean enable) {
- mCheatSheetEnabled = enable;
+ return showDialogInternal() || handled;
}
@Override
@@ -188,87 +233,9 @@
}
cheatSheet.show();
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-
return true;
}
- public void setRouteTypes(int types) {
- if (types == mRouteTypes) {
- // Already registered; nothing to do.
- return;
- }
-
- if (mAttachedToWindow && mRouteTypes != 0) {
- mRouter.removeCallback(mRouterCallback);
- }
-
- mRouteTypes = types;
-
- if (mAttachedToWindow) {
- updateRouteInfo();
- mRouter.addCallback(types, mRouterCallback,
- MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
- }
- }
-
- private void updateRouteInfo() {
- updateRemoteIndicator();
- updateRouteCount();
- }
-
- public int getRouteTypes() {
- return mRouteTypes;
- }
-
- void updateRemoteIndicator() {
- final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
- final boolean isRemote = selected != mRouter.getDefaultRoute();
- final boolean isConnecting = selected != null && selected.isConnecting();
-
- boolean needsRefresh = false;
- if (mRemoteActive != isRemote) {
- mRemoteActive = isRemote;
- needsRefresh = true;
- }
- if (mIsConnecting != isConnecting) {
- mIsConnecting = isConnecting;
- needsRefresh = true;
- }
-
- if (needsRefresh) {
- refreshDrawableState();
- }
- }
-
- void updateRouteCount() {
- final int N = mRouter.getRouteCount();
- int count = 0;
- boolean scanRequired = false;
- for (int i = 0; i < N; i++) {
- final RouteInfo route = mRouter.getRouteAt(i);
- final int routeTypes = route.getSupportedTypes();
- if ((routeTypes & mRouteTypes) != 0) {
- if (route instanceof RouteGroup) {
- count += ((RouteGroup) route).getRouteCount();
- } else {
- count++;
- }
- if (((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO
- | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) {
- scanRequired = true;
- }
- }
- }
-
- setEnabled(count != 0);
-
- // Only allow toggling if we have more than just user routes.
- // Don't toggle if we support video or remote display routes, we may have to
- // let the dialog scan.
- mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0
- && !scanRequired;
- }
-
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
@@ -296,6 +263,21 @@
}
}
+ private void setRemoteIndicatorDrawable(Drawable d) {
+ if (mRemoteIndicator != null) {
+ mRemoteIndicator.setCallback(null);
+ unscheduleDrawable(mRemoteIndicator);
+ }
+ mRemoteIndicator = d;
+ if (d != null) {
+ d.setCallback(this);
+ d.setState(getDrawableState());
+ d.setVisible(getVisibility() == VISIBLE, false);
+ }
+
+ refreshDrawableState();
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mRemoteIndicator;
@@ -304,12 +286,16 @@
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
- if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState();
+
+ if (mRemoteIndicator != null) {
+ mRemoteIndicator.jumpToCurrentState();
+ }
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
+
if (mRemoteIndicator != null) {
mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
}
@@ -318,20 +304,22 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+
mAttachedToWindow = true;
if (mRouteTypes != 0) {
- mRouter.addCallback(mRouteTypes, mRouterCallback,
+ mRouter.addCallback(mRouteTypes, mCallback,
MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
- updateRouteInfo();
}
+ refreshRoute();
}
@Override
public void onDetachedFromWindow() {
- if (mRouteTypes != 0) {
- mRouter.removeCallback(mRouterCallback);
- }
mAttachedToWindow = false;
+ if (mRouteTypes != 0) {
+ mRouter.removeCallback(mCallback);
+ }
+
super.onDetachedFromWindow();
}
@@ -394,93 +382,71 @@
final int drawLeft = left + (right - left - drawWidth) / 2;
final int drawTop = top + (bottom - top - drawHeight) / 2;
- mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight);
+ mRemoteIndicator.setBounds(drawLeft, drawTop,
+ drawLeft + drawWidth, drawTop + drawHeight);
mRemoteIndicator.draw(canvas);
}
- public void setExtendedSettingsClickListener(OnClickListener listener) {
- mExtendedSettingsClickListener = listener;
- if (mDialogFragment != null) {
- mDialogFragment.setExtendedSettingsClickListener(listener);
- }
- }
+ private void refreshRoute() {
+ if (mAttachedToWindow) {
+ final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+ final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes);
+ final boolean isConnecting = isRemote && route.isConnecting();
- /**
- * Asynchronously show the route chooser dialog.
- * This will attach a {@link DialogFragment} to the containing Activity.
- */
- public void showDialog() {
- final FragmentManager fm = getActivity().getFragmentManager();
- if (mDialogFragment == null) {
- // See if one is already attached to this activity.
- mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
- MediaRouteChooserDialogFragment.FRAGMENT_TAG);
- }
- if (mDialogFragment != null) {
- Log.w(TAG, "showDialog(): Already showing!");
- return;
- }
-
- mDialogFragment = new MediaRouteChooserDialogFragment();
- mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
- mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() {
- @Override
- public void onDetached(MediaRouteChooserDialogFragment detachedFragment) {
- mDialogFragment = null;
+ boolean needsRefresh = false;
+ if (mRemoteActive != isRemote) {
+ mRemoteActive = isRemote;
+ needsRefresh = true;
}
- });
- mDialogFragment.setRouteTypes(mRouteTypes);
- mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
+ if (mIsConnecting != isConnecting) {
+ mIsConnecting = isConnecting;
+ needsRefresh = true;
+ }
+
+ if (needsRefresh) {
+ refreshDrawableState();
+ }
+
+ setEnabled(mRouter.isRouteAvailable(mRouteTypes,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
+ }
}
- private Activity getActivity() {
- // Gross way of unwrapping the Activity so we can get the FragmentManager
- Context context = getContext();
- while (context instanceof ContextWrapper && !(context instanceof Activity)) {
- context = ((ContextWrapper) context).getBaseContext();
- }
- if (!(context instanceof Activity)) {
- throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
- }
-
- return (Activity) context;
- }
-
- private class MediaRouteCallback extends MediaRouter.SimpleCallback {
- @Override
- public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
- updateRemoteIndicator();
- }
-
- @Override
- public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
- updateRemoteIndicator();
- }
-
- @Override
- public void onRouteChanged(MediaRouter router, RouteInfo info) {
- updateRemoteIndicator();
- }
-
+ private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
- updateRouteCount();
+ refreshRoute();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo info) {
- updateRouteCount();
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+ refreshRoute();
}
@Override
public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
int index) {
- updateRouteCount();
+ refreshRoute();
}
@Override
public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
- updateRouteCount();
+ refreshRoute();
}
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index cfd7e4a..79d5e5d 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -715,6 +715,7 @@
}
d.setLevel(mLevel);
d.setLayoutDirection(getLayoutDirection());
+ d.setVisible(getVisibility() == VISIBLE, true);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyColorMod();
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
new file mode 100644
index 0000000..b963c74
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.text.TextUtils;
+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.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This class implements the route chooser dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to choose a route that matches a given selector.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteChooserDialog extends Dialog {
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+
+ private int mRouteTypes;
+ private View.OnClickListener mExtendedSettingsClickListener;
+ private RouteAdapter mAdapter;
+ private ListView mListView;
+ private Button mExtendedSettingsButton;
+ private boolean mAttachedToWindow;
+
+ public MediaRouteChooserDialog(Context context, int theme) {
+ super(context, theme);
+
+ mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ mCallback = new MediaRouterCallback();
+ }
+
+ /**
+ * Gets the media route types for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @return The route types.
+ */
+ public int getRouteTypes() {
+ return mRouteTypes;
+ }
+
+ /**
+ * Sets the types of routes that will be shown in the media route chooser dialog
+ * launched by this button.
+ *
+ * @param types The route types to match.
+ */
+ public void setRouteTypes(int types) {
+ if (mRouteTypes != types) {
+ mRouteTypes = types;
+
+ if (mAttachedToWindow) {
+ mRouter.removeCallback(mCallback);
+ mRouter.addCallback(types, mCallback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ }
+
+ refreshRoutes();
+ }
+ }
+
+ public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+ if (listener != mExtendedSettingsClickListener) {
+ mExtendedSettingsClickListener = listener;
+ updateExtendedSettingsButton();
+ }
+ }
+
+ /**
+ * Returns true if the route should be included in the list.
+ * <p>
+ * The default implementation returns true for non-default routes that
+ * match the selector. Subclasses can override this method to filter routes
+ * differently.
+ * </p>
+ *
+ * @param route The route to consider, never null.
+ * @return True if the route should be included in the chooser dialog.
+ */
+ public boolean onFilterRoute(MediaRouter.RouteInfo route) {
+ return !route.isDefault() && route.matchesTypes(mRouteTypes);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+
+ setContentView(R.layout.media_route_chooser_dialog);
+ setTitle(R.string.media_route_chooser_title);
+
+ // Must be called after setContentView.
+ getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+ R.drawable.ic_media_route_off_holo_dark);
+
+ mAdapter = new RouteAdapter(getContext());
+ mListView = (ListView)findViewById(R.id.media_route_list);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(mAdapter);
+ mListView.setEmptyView(findViewById(android.R.id.empty));
+
+ mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button);
+ updateExtendedSettingsButton();
+ }
+
+ private void updateExtendedSettingsButton() {
+ if (mExtendedSettingsButton != null) {
+ mExtendedSettingsButton.setOnClickListener(mExtendedSettingsClickListener);
+ mExtendedSettingsButton.setVisibility(
+ mExtendedSettingsClickListener != null ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mAttachedToWindow = true;
+ mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ refreshRoutes();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mAttachedToWindow = false;
+ mRouter.removeCallback(mCallback);
+
+ super.onDetachedFromWindow();
+ }
+
+ /**
+ * Refreshes the list of routes that are shown in the chooser dialog.
+ */
+ public void refreshRoutes() {
+ if (mAttachedToWindow) {
+ mAdapter.update();
+ }
+ }
+
+ private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
+ implements ListView.OnItemClickListener {
+ private final LayoutInflater mInflater;
+
+ public RouteAdapter(Context context) {
+ super(context, 0);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void update() {
+ clear();
+ final int count = mRouter.getRouteCount();
+ for (int i = 0; i < count; i++) {
+ MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
+ if (onFilterRoute(route)) {
+ add(route);
+ }
+ }
+ sort(RouteComparator.sInstance);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.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);
+ text1.setText(route.getName());
+ CharSequence description = route.getDescription();
+ if (TextUtils.isEmpty(description)) {
+ text2.setVisibility(View.GONE);
+ text2.setText("");
+ } else {
+ text2.setVisibility(View.VISIBLE);
+ text2.setText(description);
+ }
+ view.setEnabled(route.isEnabled());
+ return view;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MediaRouter.RouteInfo route = getItem(position);
+ if (route.isEnabled()) {
+ route.select();
+ dismiss();
+ }
+ }
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+ dismiss();
+ }
+ }
+
+ private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+ public static final RouteComparator sInstance = new RouteComparator();
+
+ @Override
+ public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+ return lhs.getName().toString().compareTo(rhs.getName().toString());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index 268dcf6..ae362af 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -16,675 +16,86 @@
package com.android.internal.app;
-import com.android.internal.R;
-
-import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.MediaRouteActionProvider;
-import android.app.MediaRouteButton;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
-import android.media.MediaRouter;
-import android.media.MediaRouter.RouteCategory;
-import android.media.MediaRouter.RouteGroup;
-import android.media.MediaRouter.RouteInfo;
import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.CheckBox;
-import android.widget.Checkable;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
+import android.view.View.OnClickListener;
/**
- * This class implements the route chooser dialog for {@link MediaRouter}.
+ * Media route chooser dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteChooserDialog}. The application may subclass
+ * this dialog fragment to customize the media route chooser dialog.
+ * </p>
*
- * @see MediaRouteButton
- * @see MediaRouteActionProvider
+ * TODO: Move this back into the API, as in the support library media router.
*/
public class MediaRouteChooserDialogFragment extends DialogFragment {
- private static final String TAG = "MediaRouteChooserDialogFragment";
- public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment";
+ private final String ARGUMENT_ROUTE_TYPES = "routeTypes";
- private static final int[] ITEM_LAYOUTS = new int[] {
- R.layout.media_route_list_item_top_header,
- R.layout.media_route_list_item_section_header,
- R.layout.media_route_list_item,
- R.layout.media_route_list_item_checkable,
- R.layout.media_route_list_item_collapse_group
- };
+ private View.OnClickListener mExtendedSettingsClickListener;
- MediaRouter mRouter;
- private int mRouteTypes;
-
- private LayoutInflater mInflater;
- private LauncherListener mLauncherListener;
- private View.OnClickListener mExtendedSettingsListener;
- private RouteAdapter mAdapter;
- private ListView mListView;
- private SeekBar mVolumeSlider;
- private ImageView mVolumeIcon;
-
- final RouteComparator mComparator = new RouteComparator();
- final MediaRouterCallback mCallback = new MediaRouterCallback();
- private boolean mIgnoreSliderVolumeChanges;
- private boolean mIgnoreCallbackVolumeChanges;
-
+ /**
+ * Creates a media route chooser dialog fragment.
+ * <p>
+ * All subclasses of this class must also possess a default constructor.
+ * </p>
+ */
public MediaRouteChooserDialogFragment() {
- setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
+ setCancelable(true);
+ setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog);
}
- public void setLauncherListener(LauncherListener listener) {
- mLauncherListener = listener;
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (mLauncherListener != null) {
- mLauncherListener.onDetached(this);
- }
- if (mAdapter != null) {
- mAdapter = null;
- }
- mInflater = null;
- mRouter.removeCallback(mCallback);
- mRouter = null;
- }
-
- public void setExtendedSettingsClickListener(View.OnClickListener listener) {
- mExtendedSettingsListener = listener;
+ public int getRouteTypes() {
+ Bundle args = getArguments();
+ return args != null ? args.getInt(ARGUMENT_ROUTE_TYPES) : 0;
}
public void setRouteTypes(int types) {
- mRouteTypes = types;
- }
-
- void updateVolume() {
- if (mRouter == null) return;
-
- final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
- mVolumeIcon.setImageResource(selectedRoute == null ||
- selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ?
- R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark);
-
- mIgnoreSliderVolumeChanges = true;
-
- if (selectedRoute == null ||
- selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) {
- // Disable the slider and show it at max volume.
- mVolumeSlider.setMax(1);
- mVolumeSlider.setProgress(1);
- mVolumeSlider.setEnabled(false);
- } else {
- mVolumeSlider.setEnabled(true);
- mVolumeSlider.setMax(selectedRoute.getVolumeMax());
- mVolumeSlider.setProgress(selectedRoute.getVolume());
- }
-
- mIgnoreSliderVolumeChanges = false;
- }
-
- void changeVolume(int newValue) {
- if (mIgnoreSliderVolumeChanges) return;
-
- final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
- if (selectedRoute != null &&
- selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
- final int maxVolume = selectedRoute.getVolumeMax();
- newValue = Math.max(0, Math.min(newValue, maxVolume));
- selectedRoute.requestSetVolume(newValue);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mInflater = inflater;
- final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false);
-
- mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon);
- mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider);
- updateVolume();
- mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener());
-
- if (mExtendedSettingsListener != null) {
- final View extendedSettingsButton = layout.findViewById(R.id.extended_settings);
- extendedSettingsButton.setVisibility(View.VISIBLE);
- extendedSettingsButton.setOnClickListener(mExtendedSettingsListener);
- }
-
- final ListView list = (ListView) layout.findViewById(R.id.list);
- list.setItemsCanFocus(true);
- list.setAdapter(mAdapter = new RouteAdapter());
- list.setOnItemClickListener(mAdapter);
-
- mListView = list;
-
- mAdapter.scrollToSelectedItem();
-
- return layout;
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new RouteChooserDialog(getActivity(), getTheme());
- }
-
- private static class ViewHolder {
- public TextView text1;
- public TextView text2;
- public ImageView icon;
- public ImageButton expandGroupButton;
- public RouteAdapter.ExpandGroupListener expandGroupListener;
- public int position;
- public CheckBox check;
- }
-
- private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener {
- private static final int VIEW_TOP_HEADER = 0;
- private static final int VIEW_SECTION_HEADER = 1;
- private static final int VIEW_ROUTE = 2;
- private static final int VIEW_GROUPING_ROUTE = 3;
- private static final int VIEW_GROUPING_DONE = 4;
-
- private int mSelectedItemPosition = -1;
- private final ArrayList<Object> mItems = new ArrayList<Object>();
-
- private RouteCategory mCategoryEditingGroups;
- private RouteGroup mEditingGroup;
-
- // Temporary lists for manipulation
- private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>();
- private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>();
-
- private boolean mIgnoreUpdates;
-
- RouteAdapter() {
- update();
- }
-
- void update() {
- /*
- * This is kind of wacky, but our data sets are going to be
- * fairly small on average. Ideally we should be able to do some of this stuff
- * in-place instead.
- *
- * Basic idea: each entry in mItems represents an item in the list for quick access.
- * Entries can be a RouteCategory (section header), a RouteInfo with a category of
- * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing
- * the user to change the group),
- */
- if (mIgnoreUpdates) return;
-
- mItems.clear();
-
- final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
- mSelectedItemPosition = -1;
-
- List<RouteInfo> routes;
- final int catCount = mRouter.getCategoryCount();
- for (int i = 0; i < catCount; i++) {
- final RouteCategory cat = mRouter.getCategoryAt(i);
- routes = cat.getRoutes(mCatRouteList);
-
- if (!cat.isSystem()) {
- mItems.add(cat);
- }
-
- if (cat == mCategoryEditingGroups) {
- addGroupEditingCategoryRoutes(routes);
- } else {
- addSelectableRoutes(selectedRoute, routes);
- }
-
- routes.clear();
+ if (types != getRouteTypes()) {
+ Bundle args = getArguments();
+ if (args == null) {
+ args = new Bundle();
}
+ args.putInt(ARGUMENT_ROUTE_TYPES, types);
+ setArguments(args);
- notifyDataSetChanged();
- if (mListView != null && mSelectedItemPosition >= 0) {
- mListView.setItemChecked(mSelectedItemPosition, true);
- }
- }
-
- void scrollToEditingGroup() {
- if (mCategoryEditingGroups == null || mListView == null) return;
-
- int pos = 0;
- int bound = 0;
- final int itemCount = mItems.size();
- for (int i = 0; i < itemCount; i++) {
- final Object item = mItems.get(i);
- if (item != null && item == mCategoryEditingGroups) {
- bound = i;
- }
- if (item == null) {
- pos = i;
- break; // this is always below the category header; we can stop here.
- }
- }
-
- mListView.smoothScrollToPosition(pos, bound);
- }
-
- void scrollToSelectedItem() {
- if (mListView == null || mSelectedItemPosition < 0) return;
-
- mListView.smoothScrollToPosition(mSelectedItemPosition);
- }
-
- void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) {
- final int routeCount = from.size();
- for (int j = 0; j < routeCount; j++) {
- final RouteInfo info = from.get(j);
- if (info == selectedRoute) {
- mSelectedItemPosition = mItems.size();
- }
- mItems.add(info);
- }
- }
-
- void addGroupEditingCategoryRoutes(List<RouteInfo> from) {
- // Unpack groups and flatten for presentation
- // mSortRouteList will always be empty here.
- final int topCount = from.size();
- for (int i = 0; i < topCount; i++) {
- final RouteInfo route = from.get(i);
- final RouteGroup group = route.getGroup();
- if (group == route) {
- // This is a group, unpack it.
- final int groupCount = group.getRouteCount();
- for (int j = 0; j < groupCount; j++) {
- final RouteInfo innerRoute = group.getRouteAt(j);
- mSortRouteList.add(innerRoute);
- }
- } else {
- mSortRouteList.add(route);
- }
- }
- // Sort by name. This will keep the route positions relatively stable even though they
- // will be repeatedly added and removed.
- Collections.sort(mSortRouteList, mComparator);
-
- mItems.addAll(mSortRouteList);
- mSortRouteList.clear();
-
- mItems.add(null); // Sentinel reserving space for the "done" button.
- }
-
- @Override
- public int getCount() {
- return mItems.size();
- }
-
- @Override
- public int getViewTypeCount() {
- return 5;
- }
-
- @Override
- public int getItemViewType(int position) {
- final Object item = getItem(position);
- if (item instanceof RouteCategory) {
- return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER;
- } else if (item == null) {
- return VIEW_GROUPING_DONE;
- } else {
- final RouteInfo info = (RouteInfo) item;
- if (info.getCategory() == mCategoryEditingGroups) {
- return VIEW_GROUPING_ROUTE;
- }
- return VIEW_ROUTE;
- }
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public boolean isEnabled(int position) {
- switch (getItemViewType(position)) {
- case VIEW_ROUTE:
- return ((RouteInfo) mItems.get(position)).isEnabled();
- case VIEW_GROUPING_ROUTE:
- case VIEW_GROUPING_DONE:
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public Object getItem(int position) {
- return mItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final int viewType = getItemViewType(position);
-
- ViewHolder holder;
- if (convertView == null) {
- convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false);
- holder = new ViewHolder();
- holder.position = position;
- holder.text1 = (TextView) convertView.findViewById(R.id.text1);
- holder.text2 = (TextView) convertView.findViewById(R.id.text2);
- holder.icon = (ImageView) convertView.findViewById(R.id.icon);
- holder.check = (CheckBox) convertView.findViewById(R.id.check);
- holder.expandGroupButton = (ImageButton) convertView.findViewById(
- R.id.expand_button);
- if (holder.expandGroupButton != null) {
- holder.expandGroupListener = new ExpandGroupListener();
- holder.expandGroupButton.setOnClickListener(holder.expandGroupListener);
- }
-
- final View fview = convertView;
- final ListView list = (ListView) parent;
- final ViewHolder fholder = holder;
- convertView.setOnClickListener(new View.OnClickListener() {
- @Override public void onClick(View v) {
- list.performItemClick(fview, fholder.position, 0);
- }
- });
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder) convertView.getTag();
- holder.position = position;
- }
-
- switch (viewType) {
- case VIEW_ROUTE:
- case VIEW_GROUPING_ROUTE:
- bindItemView(position, holder);
- break;
- case VIEW_SECTION_HEADER:
- case VIEW_TOP_HEADER:
- bindHeaderView(position, holder);
- break;
- }
-
- convertView.setActivated(position == mSelectedItemPosition);
- convertView.setEnabled(isEnabled(position));
-
- return convertView;
- }
-
- void bindItemView(int position, ViewHolder holder) {
- RouteInfo info = (RouteInfo) mItems.get(position);
- holder.text1.setText(info.getName(getActivity()));
- final CharSequence status = info.getStatus();
- if (TextUtils.isEmpty(status)) {
- holder.text2.setVisibility(View.GONE);
- } else {
- holder.text2.setVisibility(View.VISIBLE);
- holder.text2.setText(status);
- }
- Drawable icon = info.getIconDrawable();
- if (icon != null) {
- // Make sure we have a fresh drawable where it doesn't matter if we mutate it
- icon = icon.getConstantState().newDrawable(getResources());
- }
- holder.icon.setImageDrawable(icon);
- holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
-
- RouteCategory cat = info.getCategory();
- boolean canGroup = false;
- if (cat == mCategoryEditingGroups) {
- RouteGroup group = info.getGroup();
- holder.check.setEnabled(group.getRouteCount() > 1);
- holder.check.setChecked(group == mEditingGroup);
- } else {
- if (cat.isGroupable()) {
- final RouteGroup group = (RouteGroup) info;
- canGroup = group.getRouteCount() > 1 ||
- getItemViewType(position - 1) == VIEW_ROUTE ||
- (position < getCount() - 1 &&
- getItemViewType(position + 1) == VIEW_ROUTE);
- }
- }
-
- if (holder.expandGroupButton != null) {
- holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE);
- holder.expandGroupListener.position = position;
- }
- }
-
- void bindHeaderView(int position, ViewHolder holder) {
- RouteCategory cat = (RouteCategory) mItems.get(position);
- holder.text1.setText(cat.getName(getActivity()));
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final int type = getItemViewType(position);
- if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) {
- return;
- } else if (type == VIEW_GROUPING_DONE) {
- finishGrouping();
- return;
- } else {
- final Object item = getItem(position);
- if (!(item instanceof RouteInfo)) {
- // Oops. Stale event running around? Skip it.
- return;
- }
-
- final RouteInfo route = (RouteInfo) item;
- if (type == VIEW_ROUTE) {
- mRouter.selectRouteInt(mRouteTypes, route, true);
- dismiss();
- } else if (type == VIEW_GROUPING_ROUTE) {
- final Checkable c = (Checkable) view;
- final boolean wasChecked = c.isChecked();
-
- mIgnoreUpdates = true;
- RouteGroup oldGroup = route.getGroup();
- if (!wasChecked && oldGroup != mEditingGroup) {
- // Assumption: in a groupable category oldGroup will never be null.
- if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
- // Old group was selected but is now empty. Select the group
- // we're manipulating since that's where the last route went.
- mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
- }
- oldGroup.removeRoute(route);
- mEditingGroup.addRoute(route);
- c.setChecked(true);
- } else if (wasChecked && mEditingGroup.getRouteCount() > 1) {
- mEditingGroup.removeRoute(route);
-
- // In a groupable category this will add
- // the route into its own new group.
- mRouter.addRouteInt(route);
- }
- mIgnoreUpdates = false;
- update();
- }
- }
- }
-
- boolean isGrouping() {
- return mCategoryEditingGroups != null;
- }
-
- void finishGrouping() {
- mCategoryEditingGroups = null;
- mEditingGroup = null;
- getDialog().setCanceledOnTouchOutside(true);
- update();
- scrollToSelectedItem();
- }
-
- class ExpandGroupListener implements View.OnClickListener {
- int position;
-
- @Override
- public void onClick(View v) {
- // Assumption: this is only available for the user to click if we're presenting
- // a groupable category, where every top-level route in the category is a group.
- final RouteGroup group = (RouteGroup) getItem(position);
- mEditingGroup = group;
- mCategoryEditingGroups = group.getCategory();
- getDialog().setCanceledOnTouchOutside(false);
- mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
- update();
- scrollToEditingGroup();
+ MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+ if (dialog != null) {
+ dialog.setRouteTypes(types);
}
}
}
- class MediaRouterCallback extends MediaRouter.Callback {
- @Override
- public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
- mAdapter.update();
- updateVolume();
- }
+ public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+ if (listener != mExtendedSettingsClickListener) {
+ mExtendedSettingsClickListener = listener;
- @Override
- public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
- mAdapter.update();
- }
-
- @Override
- public void onRouteAdded(MediaRouter router, RouteInfo info) {
- mAdapter.update();
- }
-
- @Override
- public void onRouteRemoved(MediaRouter router, RouteInfo info) {
- if (info == mAdapter.mEditingGroup) {
- mAdapter.finishGrouping();
- }
- mAdapter.update();
- }
-
- @Override
- public void onRouteChanged(MediaRouter router, RouteInfo info) {
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onRouteGrouped(MediaRouter router, RouteInfo info,
- RouteGroup group, int index) {
- mAdapter.update();
- }
-
- @Override
- public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
- mAdapter.update();
- }
-
- @Override
- public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
- if (!mIgnoreCallbackVolumeChanges) {
- updateVolume();
- }
- }
- }
-
- class RouteComparator implements Comparator<RouteInfo> {
- @Override
- public int compare(RouteInfo lhs, RouteInfo rhs) {
- return lhs.getName(getActivity()).toString()
- .compareTo(rhs.getName(getActivity()).toString());
- }
- }
-
- class RouteChooserDialog extends Dialog {
- public RouteChooserDialog(Context context, int theme) {
- super(context, theme);
- }
-
- @Override
- public void onBackPressed() {
- if (mAdapter != null && mAdapter.isGrouping()) {
- mAdapter.finishGrouping();
- } else {
- super.onBackPressed();
- }
- }
-
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
- final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
- if (selectedRoute != null) {
- selectedRoute.requestUpdateVolume(-1);
- return true;
- }
- } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
- final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
- if (selectedRoute != null) {
- mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1);
- return true;
- }
- }
- return super.onKeyDown(keyCode, event);
- }
-
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
- return true;
- } else {
- return super.onKeyUp(keyCode, event);
+ MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+ if (dialog != null) {
+ dialog.setExtendedSettingsClickListener(listener);
}
}
}
/**
- * Implemented by the MediaRouteButton that launched this dialog
+ * Called when the chooser dialog is being created.
+ * <p>
+ * Subclasses may override this method to customize the dialog.
+ * </p>
*/
- public interface LauncherListener {
- public void onDetached(MediaRouteChooserDialogFragment detachedFragment);
+ public MediaRouteChooserDialog onCreateChooserDialog(
+ Context context, Bundle savedInstanceState) {
+ return new MediaRouteChooserDialog(context, getTheme());
}
- class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener {
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- changeVolume(progress);
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- mIgnoreCallbackVolumeChanges = true;
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- mIgnoreCallbackVolumeChanges = false;
- updateVolume();
- }
-
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ MediaRouteChooserDialog dialog = onCreateChooserDialog(getActivity(), savedInstanceState);
+ dialog.setRouteTypes(getRouteTypes());
+ dialog.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
+ return dialog;
}
}
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
new file mode 100644
index 0000000..8fc99c7
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -0,0 +1,318 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.app.MediaRouteActionProvider;
+import android.app.MediaRouteButton;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteGroup;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+/**
+ * This class implements the route controller dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to control or disconnect from the currently selected route.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteControllerDialog extends Dialog {
+ // 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.
+ private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
+
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+ private final MediaRouter.RouteInfo mRoute;
+
+ private boolean mCreated;
+ private Drawable mMediaRouteConnectingDrawable;
+ private Drawable mMediaRouteOnDrawable;
+ private Drawable mCurrentIconDrawable;
+
+ private boolean mVolumeControlEnabled = true;
+ private LinearLayout mVolumeLayout;
+ private SeekBar mVolumeSlider;
+ private boolean mVolumeSliderTouched;
+
+ private View mControlView;
+
+ private Button mDisconnectButton;
+
+ public MediaRouteControllerDialog(Context context, int theme) {
+ super(context, theme);
+
+ mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ mCallback = new MediaRouterCallback();
+ mRoute = mRouter.getSelectedRoute();
+ }
+
+ /**
+ * Gets the route that this dialog is controlling.
+ */
+ public MediaRouter.RouteInfo getRoute() {
+ return mRoute;
+ }
+
+ /**
+ * Provides the subclass an opportunity to create a view that will
+ * be included within the body of the dialog to offer additional media controls
+ * for the currently playing content.
+ *
+ * @param savedInstanceState The dialog's saved instance state.
+ * @return The media control view, or null if none.
+ */
+ public View onCreateMediaControlView(Bundle savedInstanceState) {
+ return null;
+ }
+
+ /**
+ * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
+ *
+ * @return The media control view, or null if none.
+ */
+ public View getMediaControlView() {
+ return mControlView;
+ }
+
+ /**
+ * Sets whether to enable the volume slider and volume control using the volume keys
+ * when the route supports it.
+ * <p>
+ * The default value is true.
+ * </p>
+ */
+ public void setVolumeControlEnabled(boolean enable) {
+ if (mVolumeControlEnabled != enable) {
+ mVolumeControlEnabled = enable;
+ if (mCreated) {
+ updateVolume();
+ }
+ }
+ }
+
+ /**
+ * Returns whether to enable the volume slider and volume control using the volume keys
+ * when the route supports it.
+ */
+ public boolean isVolumeControlEnabled() {
+ return mVolumeControlEnabled;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+
+ setContentView(R.layout.media_route_controller_dialog);
+
+ mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout);
+ mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider);
+ mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ private final Runnable mStopTrackingTouch = new Runnable() {
+ @Override
+ public void run() {
+ if (mVolumeSliderTouched) {
+ mVolumeSliderTouched = false;
+ updateVolume();
+ }
+ }
+ };
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mVolumeSliderTouched) {
+ mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+ } else {
+ mVolumeSliderTouched = true;
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Defer resetting mVolumeSliderTouched to allow the media route provider
+ // a little time to settle into its new state and publish the final
+ // volume update.
+ mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ mRoute.requestSetVolume(progress);
+ }
+ }
+ });
+
+ mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button);
+ mDisconnectButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mRoute.isSelected()) {
+ mRouter.getDefaultRoute().select();
+ }
+ dismiss();
+ }
+ });
+
+ mCreated = true;
+ if (update()) {
+ mControlView = onCreateMediaControlView(savedInstanceState);
+ FrameLayout controlFrame =
+ (FrameLayout)findViewById(R.id.media_route_control_frame);
+ if (mControlView != null) {
+ controlFrame.addView(mControlView);
+ controlFrame.setVisibility(View.VISIBLE);
+ } else {
+ controlFrame.setVisibility(View.GONE);
+ }
+ }
+ }
+
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+ update();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mRouter.removeCallback(mCallback);
+
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ private boolean update() {
+ if (!mRoute.isSelected() || mRoute.isDefault()) {
+ dismiss();
+ return false;
+ }
+
+ setTitle(mRoute.getName());
+ updateVolume();
+
+ Drawable icon = getIconDrawable();
+ if (icon != mCurrentIconDrawable) {
+ mCurrentIconDrawable = icon;
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon);
+ }
+ return true;
+ }
+
+ private Drawable getIconDrawable() {
+ if (mRoute.isConnecting()) {
+ if (mMediaRouteConnectingDrawable == null) {
+ mMediaRouteConnectingDrawable = getContext().getResources().getDrawable(
+ R.drawable.ic_media_route_connecting_holo_dark);
+ }
+ return mMediaRouteConnectingDrawable;
+ } else {
+ if (mMediaRouteOnDrawable == null) {
+ mMediaRouteOnDrawable = getContext().getResources().getDrawable(
+ R.drawable.ic_media_route_on_holo_dark);
+ }
+ return mMediaRouteOnDrawable;
+ }
+ }
+
+ private void updateVolume() {
+ if (!mVolumeSliderTouched) {
+ if (isVolumeControlAvailable()) {
+ mVolumeLayout.setVisibility(View.VISIBLE);
+ mVolumeSlider.setMax(mRoute.getVolumeMax());
+ mVolumeSlider.setProgress(mRoute.getVolume());
+ } else {
+ mVolumeLayout.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private boolean isVolumeControlAvailable() {
+ return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
+ MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+ @Override
+ public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+ update();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+ update();
+ }
+
+ @Override
+ public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+ if (route == mRoute) {
+ updateVolume();
+ }
+ }
+
+ @Override
+ public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+ int index) {
+ update();
+ }
+
+ @Override
+ public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+ update();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java
new file mode 100644
index 0000000..108e81f
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.app;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Media route controller dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteControllerDialog}. The application may subclass
+ * this dialog fragment to customize the media route controller dialog.
+ * </p>
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteControllerDialogFragment extends DialogFragment {
+ /**
+ * Creates a media route controller dialog fragment.
+ * <p>
+ * All subclasses of this class must also possess a default constructor.
+ * </p>
+ */
+ public MediaRouteControllerDialogFragment() {
+ setCancelable(true);
+ setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog);
+ }
+
+ /**
+ * Called when the controller dialog is being created.
+ * <p>
+ * Subclasses may override this method to customize the dialog.
+ * </p>
+ */
+ public MediaRouteControllerDialog onCreateControllerDialog(
+ Context context, Bundle savedInstanceState) {
+ return new MediaRouteControllerDialog(context, getTheme());
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return onCreateControllerDialog(getActivity(), savedInstanceState);
+ }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
new file mode 100644
index 0000000..fad7fd4
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.app;
+
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.media.MediaRouter;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Shows media route dialog as appropriate.
+ * @hide
+ */
+public abstract class MediaRouteDialogPresenter {
+ private static final String TAG = "MediaRouter";
+
+ private static final String CHOOSER_FRAGMENT_TAG =
+ "android.app.MediaRouteButton:MediaRouteChooserDialogFragment";
+ private static final String CONTROLLER_FRAGMENT_TAG =
+ "android.app.MediaRouteButton:MediaRouteControllerDialogFragment";
+
+ public static DialogFragment showDialogFragment(Activity activity,
+ int routeTypes, View.OnClickListener extendedSettingsClickListener) {
+ final MediaRouter router = (MediaRouter)activity.getSystemService(
+ Context.MEDIA_ROUTER_SERVICE);
+ final FragmentManager fm = activity.getFragmentManager();
+
+ MediaRouter.RouteInfo route = router.getSelectedRoute();
+ if (route.isDefault() || !route.matchesTypes(routeTypes)) {
+ if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
+ Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+ return null;
+ }
+ MediaRouteChooserDialogFragment f = new MediaRouteChooserDialogFragment();
+ f.setRouteTypes(routeTypes);
+ f.setExtendedSettingsClickListener(extendedSettingsClickListener);
+ f.show(fm, CHOOSER_FRAGMENT_TAG);
+ return f;
+ } else {
+ if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
+ Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+ return null;
+ }
+ MediaRouteControllerDialogFragment f = new MediaRouteControllerDialogFragment();
+ f.show(fm, CONTROLLER_FRAGMENT_TAG);
+ return f;
+ }
+ }
+
+ public static Dialog createDialog(Context context,
+ int routeTypes, View.OnClickListener extendedSettingsClickListener) {
+ final MediaRouter router = (MediaRouter)context.getSystemService(
+ Context.MEDIA_ROUTER_SERVICE);
+
+ MediaRouter.RouteInfo route = router.getSelectedRoute();
+ if (route.isDefault() || !route.matchesTypes(routeTypes)) {
+ final MediaRouteChooserDialog d = new MediaRouteChooserDialog(
+ context, android.R.style.Theme_DeviceDefault_Dialog);
+ d.setRouteTypes(routeTypes);
+ d.setExtendedSettingsClickListener(extendedSettingsClickListener);
+ return d;
+ } else {
+ MediaRouteControllerDialog d = new MediaRouteControllerDialog(
+ context, android.R.style.Theme_DeviceDefault_Dialog);
+ return d;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java
deleted file mode 100644
index 1a57e4e..0000000
--- a/core/java/com/android/internal/view/CheckableLinearLayout.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.internal.view;
-
-import com.android.internal.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.CheckBox;
-import android.widget.LinearLayout;
-
-public class CheckableLinearLayout extends LinearLayout implements Checkable {
- private CheckBox mCheckBox;
-
- public CheckableLinearLayout(Context context) {
- super(context);
- // TODO Auto-generated constructor stub
- }
-
- public CheckableLinearLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
-
- public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- // TODO Auto-generated constructor stub
- }
-
- public CheckableLinearLayout(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- // TODO Auto-generated constructor stub
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mCheckBox = (CheckBox) findViewById(R.id.check);
- }
-
- @Override
- public void setChecked(boolean checked) {
- mCheckBox.setChecked(checked);
- }
-
- @Override
- public boolean isChecked() {
- return mCheckBox.isChecked();
- }
-
- @Override
- public void toggle() {
- mCheckBox.toggle();
- }
-}
diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png
deleted file mode 100644
index 89abf2c..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png
deleted file mode 100644
index d9470b2..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png
deleted file mode 100644
index 34454ac..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png
deleted file mode 100644
index 8ce5a44..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
deleted file mode 100644
index 2fb7428..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png
deleted file mode 100644
index 5755b9d..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml
new file mode 100644
index 0000000..3eba9be
--- /dev/null
+++ b/core/res/res/layout/media_route_chooser_dialog.xml
@@ -0,0 +1,58 @@
+<?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="wrap_content"
+ android:orientation="vertical"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="middle">
+ <!-- List of routes. -->
+ <ListView android:id="@+id/media_route_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <!-- Content to show when list is empty. -->
+ <LinearLayout android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:orientation="horizontal"
+ 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/media_route_chooser_searching" />
+ </LinearLayout>
+
+ <!-- Settings button. -->
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?attr/buttonBarStyle">
+ <Button android:id="@+id/media_route_extended_settings_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="?attr/buttonBarButtonStyle"
+ android:gravity="center"
+ android:text="@string/media_route_chooser_extended_settings"
+ android:visibility="gone" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml
deleted file mode 100644
index 5fcb8c8..0000000
--- a/core/res/res/layout/media_route_chooser_layout.xml
+++ /dev/null
@@ -1,48 +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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:showDividers="middle"
- android:divider="?android:attr/dividerHorizontal">
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:gravity="center_vertical"
- android:padding="8dp">
- <ImageView android:id="@+id/volume_icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:src="@android:drawable/ic_audio_vol"
- android:gravity="center"
- android:scaleType="center" />
- <SeekBar android:id="@+id/volume_slider"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp" />
- <ImageButton android:id="@+id/extended_settings"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:background="?android:attr/selectableItemBackground"
- android:src="@android:drawable/ic_sysbar_quicksettings"
- android:visibility="gone" />
- </LinearLayout>
- <ListView android:id="@id/list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-</LinearLayout>
diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml
new file mode 100644
index 0000000..78287e0
--- /dev/null
+++ b/core/res/res/layout/media_route_controller_dialog.xml
@@ -0,0 +1,60 @@
+<?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="wrap_content"
+ android:orientation="vertical"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="middle">
+ <!-- Optional volume slider section. -->
+ <LinearLayout android:id="@+id/media_route_volume_layout"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:gravity="center_vertical"
+ android:padding="8dp"
+ android:visibility="gone">
+ <ImageView android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:src="@drawable/ic_audio_vol"
+ android:gravity="center"
+ android:scaleType="center" />
+ <SeekBar android:id="@+id/media_route_volume_slider"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp" />
+ </LinearLayout>
+
+ <!-- Optional content view section. -->
+ <FrameLayout android:id="@+id/media_route_control_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+ <!-- Disconnect button. -->
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?attr/buttonBarStyle">
+ <Button android:id="@+id/media_route_disconnect_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="?attr/buttonBarButtonStyle"
+ android:gravity="center"
+ android:text="@string/media_route_controller_disconnect" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index 423d544..bdca433 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -20,13 +20,6 @@
android:background="@drawable/item_background_activated_holo_dark"
android:gravity="center_vertical">
- <ImageView android:layout_width="56dp"
- android:layout_height="56dp"
- android:scaleType="center"
- android:id="@+id/icon"
- android:visibility="gone"
- android:duplicateParentState="true" />
-
<LinearLayout android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -53,14 +46,4 @@
android:duplicateParentState="true" />
</LinearLayout>
- <ImageButton
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:id="@+id/expand_button"
- android:background="?android:attr/selectableItemBackground"
- android:src="@drawable/ic_media_group_expand"
- android:scaleType="center"
- android:visibility="gone"
- android:duplicateParentState="true" />
-
</LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item_checkable.xml b/core/res/res/layout/media_route_list_item_checkable.xml
deleted file mode 100644
index 5fb82f7..0000000
--- a/core/res/res/layout/media_route_list_item_checkable.xml
+++ /dev/null
@@ -1,60 +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.
--->
-
-<com.android.internal.view.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:background="?android:attr/selectableItemBackground"
- android:gravity="center_vertical">
-
- <ImageView android:layout_width="56dp"
- android:layout_height="56dp"
- android:scaleType="center"
- android:id="@+id/icon"
- android:visibility="gone" />
-
- <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">
-
- <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" />
-
- <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" />
- </LinearLayout>
-
- <CheckBox
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp"
- android:id="@+id/check"
- android:focusable="false"
- android:clickable="false" />
-
-</com.android.internal.view.CheckableLinearLayout>
diff --git a/core/res/res/layout/media_route_list_item_collapse_group.xml b/core/res/res/layout/media_route_list_item_collapse_group.xml
deleted file mode 100644
index 323e24d..0000000
--- a/core/res/res/layout/media_route_list_item_collapse_group.xml
+++ /dev/null
@@ -1,44 +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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/selectableItemBackground">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeightSmall"
- android:background="#19ffffff"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:gravity="center_vertical">
-
- <TextView android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:text="@string/media_route_chooser_grouping_done"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/ic_media_group_collapse"
- android:scaleType="center" />
-
- </LinearLayout>
-</FrameLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/media_route_list_item_section_header.xml b/core/res/res/layout/media_route_list_item_section_header.xml
deleted file mode 100644
index 949635f..0000000
--- a/core/res/res/layout/media_route_list_item_section_header.xml
+++ /dev/null
@@ -1,34 +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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="16dp">
- <TextView
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:background="#19ffffff"
- android:textStyle="bold"
- android:textAllCaps="true"
- android:gravity="center_vertical"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:minHeight="24dp"
- />
-</FrameLayout>
diff --git a/core/res/res/layout/media_route_list_item_top_header.xml b/core/res/res/layout/media_route_list_item_top_header.xml
deleted file mode 100644
index 0c49b24..0000000
--- a/core/res/res/layout/media_route_list_item_top_header.xml
+++ /dev/null
@@ -1,29 +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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:background="#19ffffff"
- android:textStyle="bold"
- android:textAllCaps="true"
- android:gravity="center_vertical"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:minHeight="24dp"
-/>
diff --git a/core/res/res/values-mcc310-mnc260/config.xml b/core/res/res/values-mcc310-mnc260/config.xml
index 886ecbe..d602c9f 100644
--- a/core/res/res/values-mcc310-mnc260/config.xml
+++ b/core/res/res/values-mcc310-mnc260/config.xml
@@ -21,22 +21,6 @@
for different hardware and product builds. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <integer-array translatable="false" name="config_tether_upstream_types">
- <item>1</item>
- <item>4</item>
- <item>7</item>
- <item>9</item>
- </integer-array>
-
- <!-- String containing the apn value for tethering. May be overriden by secure settings
- TETHER_DUN_APN. Value is a comma separated series of strings:
- "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
- note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
- <string translatable="false" name="config_tether_apndata">T-Mobile Tethering,pcweb.tmobile.com,,,,,,,,,310,260,,DUN</string>
-
<!-- Configure mobile network MTU. Carrier specific value is set here.
-->
<integer name="config_mobile_mtu">1440</integer>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d219ec1..deff873 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4101,12 +4101,21 @@
<!-- Description of a wireless display route. [CHAR LIMIT=50] -->
<string name="wireless_display_route_description">Wireless display</string>
- <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] -->
- <string name="media_route_chooser_grouping_done">Done</string>
-
<!-- Content description of a MediaRouteButton for accessibility support -->
<string name="media_route_button_content_description">Media output</string>
+ <!-- Title of the media route chooser dialog. [CHAR LIMIT=30] -->
+ <string name="media_route_chooser_title">Connect to device</string>
+
+ <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] -->
+ <string name="media_route_chooser_searching">Searching for devices\u2026</string>
+
+ <!-- Button to access extended settings. [CHAR LIMIT=30] -->
+ <string name="media_route_chooser_extended_settings">Settings</string>
+
+ <!-- Button to disconnect from a media route. [CHAR LIMIT=30] -->
+ <string name="media_route_controller_disconnect">Disconnect</string>
+
<!-- Status message for remote routes attempting to scan/determine availability -->
<string name="media_route_status_scanning">Scanning...</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ca54c8a..72880de 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1092,6 +1092,8 @@
<java-symbol type="drawable" name="notification_template_icon_bg" />
<java-symbol type="drawable" name="notification_template_icon_low_bg" />
<java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
+ <java-symbol type="drawable" name="ic_media_route_off_holo_dark" />
+ <java-symbol type="drawable" name="ic_media_route_connecting_holo_dark" />
<java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" />
<java-symbol type="drawable" name="cling_button" />
<java-symbol type="drawable" name="cling_arrow_up" />
@@ -1249,17 +1251,16 @@
<java-symbol type="attr" name="mediaRouteButtonStyle" />
<java-symbol type="attr" name="externalRouteEnabledDrawable" />
- <java-symbol type="id" name="extended_settings" />
- <java-symbol type="id" name="check" />
- <java-symbol type="id" name="volume_slider" />
- <java-symbol type="id" name="volume_icon" />
- <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
- <java-symbol type="layout" name="media_route_chooser_layout" />
- <java-symbol type="layout" name="media_route_list_item_top_header" />
- <java-symbol type="layout" name="media_route_list_item_section_header" />
+ <java-symbol type="layout" name="media_route_chooser_dialog" />
+ <java-symbol type="layout" name="media_route_controller_dialog" />
<java-symbol type="layout" name="media_route_list_item" />
- <java-symbol type="layout" name="media_route_list_item_checkable" />
- <java-symbol type="layout" name="media_route_list_item_collapse_group" />
+ <java-symbol type="id" name="media_route_list" />
+ <java-symbol type="id" name="media_route_volume_layout" />
+ <java-symbol type="id" name="media_route_volume_slider" />
+ <java-symbol type="id" name="media_route_control_frame" />
+ <java-symbol type="id" name="media_route_disconnect_button" />
+ <java-symbol type="id" name="media_route_extended_settings_button" />
+ <java-symbol type="string" name="media_route_chooser_title" />
<java-symbol type="string" name="bluetooth_a2dp_audio_route_name" />
<java-symbol type="dimen" name="config_minScalingSpan" />
diff --git a/docs/html/about/versions/jelly-bean.jd b/docs/html/about/versions/jelly-bean.jd
index c7d1941..c6702b0 100644
--- a/docs/html/about/versions/jelly-bean.jd
+++ b/docs/html/about/versions/jelly-bean.jd
@@ -438,9 +438,9 @@
<p>Android 4.3 also includes new utilities and APIs for creating better RTL
strings and testing your localized UIs. A new <strong>BidiFormatter</strong>
-provides a set of simple APIs for wrapping Unicode strings so that you can
-fine-tune your text rendering in RTL scripts. To let you use this utility more
-broadly in your apps, the BidiFormatter APIs are also now available for earlier
+class provides a simple API for wrapping Unicode strings, so that RTL-script
+data is displayed as intended in LTR-locale messages and vice-versa. To let you use this utility more
+broadly in your apps, the BidiFormatter API is also now available for earlier
platform versions through the Support Package in the Android SDK. </p>
<p>To assist you with managing date formatting across locales, Android 4.3
diff --git a/docs/html/about/versions/kitkat.jd b/docs/html/about/versions/kitkat.jd
index 4c80d4e..5e442ec 100644
--- a/docs/html/about/versions/kitkat.jd
+++ b/docs/html/about/versions/kitkat.jd
@@ -1006,8 +1006,9 @@
<h4 id="44-pseudolocale-rtl">Force RTL Layout</h4>
<p>
- To make it easier to test and debug your layouts, Android includes a new
- developer option to force RTL layout direction in all apps.
+ To make it easier to test and debug layout mirroring issues without switching
+ to an RTL language, Android includes a new developer option to force RTL layout
+ direction in all apps.
</p>
<p>
diff --git a/docs/html/distribute/distribute_toc.cs b/docs/html/distribute/distribute_toc.cs
index ecdf2a8..b9a0eec 100644
--- a/docs/html/distribute/distribute_toc.cs
+++ b/docs/html/distribute/distribute_toc.cs
@@ -81,6 +81,7 @@
<ul>
<li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/tablets.html">Tablet Stories</a></li>
<li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/games.html">Game Stories</a></li>
+ <li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/localization.html">Localization Stories</a></li>
</ul>
</li>
diff --git a/docs/html/distribute/googleplay/publish/localizing.jd b/docs/html/distribute/googleplay/publish/localizing.jd
index 7788ce1..1a5f3c1 100644
--- a/docs/html/distribute/googleplay/publish/localizing.jd
+++ b/docs/html/distribute/googleplay/publish/localizing.jd
@@ -372,24 +372,24 @@
<p>After the translations are merged back into your app, start <a
href="#testing">testing the localized app</a>.</p>
+<h4 id="gp-trans">Purchase professional translations through Google Play
+<br />App Translation Service</h4>
+
<div class="sidebox-wrapper">
<div class="sidebox">
-<h2>Join the translation pilot</h2>
-<p>Google Play is offering translation services as part of a pilot
-program. If you're interested, sign up on the APK page in your
-Developer Console.</p>
+<h2>App Translations in Google Play</h2>
-<p>If you join, also try the <a
+<p>Hear from developers who have used the Google Play App Translation Service in <a
+href="{@docRoot}distribute/googleplay/spotlight/localization.html">Developer
+Stories: Localization in Google Play</a>.</p>
+
+<p>To make it easy to export your app's strings and import
+the finished translations into your project, try the <a
href="{@docRoot}sdk/installing/installing-adt.html#tmgr">
-ADT Translation Manager Plugin</a>, which makes it easy to upload
-your strings to the Developer Console and download translations
-right into your project. </div>
+ADT Translation Manager Plugin</a>.</div>
</div>
-<h4 id="gp-trans">Purchase professional translations through the
-Developer Console</h4>
-
-<p>Google Play can help you quickly find and purchase translations of your app.
+<p>Google Play App Translation Service can help you quickly find and purchase translations of your app.
In the Developer Console, you can browse a list of third-party vendors who are
pre-qualified by Google to offer high-quality translation at competitive prices.
You can upload the strings you want translated, select the languages you want to
diff --git a/docs/html/distribute/googleplay/spotlight/localization.jd b/docs/html/distribute/googleplay/spotlight/localization.jd
new file mode 100644
index 0000000..ae5993d
--- /dev/null
+++ b/docs/html/distribute/googleplay/spotlight/localization.jd
@@ -0,0 +1,328 @@
+page.title=Developer Stories: Localization in Google Play
+walkthru=0
+header.hide=0
+
+@jd:body
+
+<p>
+ As you build your app and distribute it across the world through Google Play,
+ localization becomes an increasingly important tool to reach more users.
+ Localization involves a <a href=
+ "{@docRoot}distribute/googleplay/publish/localizing.html">variety of tasks</a>, but
+ most important is creating quality translations of your app's UI strings and
+ marketing materials.
+</p>
+
+<p>
+ Managing the translation process across multiple languages can be a
+ challenge, especially if you need to locate translators on your own. That’s
+ why Google Play offers the App Translation Service right from the Developer
+ Console. It's a single place where you can go to source professional
+ translators, get cost estimates, and then send your strings and other
+ materials for translation.
+</p>
+
+<p>
+ Here are some stories from developers who have used Google Play's App Translation
+ Service to localize their apps and the results they've seen as they've
+ expand their offerings beyond a single language.
+</p>
+
+<!-- START STORY -->
+
+<div style="margin-bottom:2em;padding-top:10px;" id="zombieragdoll">
+
+<h3 style="line-height:1.25em">Zombie Ragdoll: Improved user engagement<br /> with localized versions</h3>
+
+ <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px height:78px;
+ width: 78px;
+ float: left;
+ margin: 12px 20px 9px 20px;" src=
+ "https://lh4.ggpht.com/m-Ew8c8C_nGctbP6PSPGOaVNnGFryReOE2yHXJ9Z6Prk1nsDyx5w5TmWfg-P5N3HypA=w124">
+
+ <div style="list-style: none;height:100%;
+ float: right;
+ border-top: 1px solid #9C0;
+ width: 220px;
+ margin: 4px 20px;padding: .5em;">
+
+ <h5>About the app</h5>
+
+ <ul>
+ <li><a href="https://play.google.com/store/apps/details?id=com.rvappstudios.zombieragdoll">Zombie Ragdoll</a></li>
+ <li>A fun zombie-based physics game</li>
+ </ul>
+
+ <h5>Localization Results</h5>
+
+ <ul>
+ <li>Increased engagement because of appeal of the localized version</li>
+ <li>80% of installs came from users of non-English languages</li>
+ </ul>
+
+ <div style="padding:.5em 0 0 1em;">
+ <a href="https://play.google.com/store/apps/details?id=com.rvappstudios.zombieragdoll">
+ <img alt="Android app on Google Play"
+ src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+ </a>
+
+ </div>
+ </div>
+
+ <div style="line-height:1.4em;">
+
+<p>
+ The 2013 Google I/O talks about <a href=
+ "https://developers.google.com/events/io/sessions/326345917">Building Android
+ Apps for a Global Audience</a> and <a href=
+ "https://developers.google.com/events/io/sessions/326455375">What’s New for
+ Developers in Google Play</a> inspired developers at RV AppStudios to go global
+ from very beginning for their new game, Zombie Ragdoll. They launched Zombie
+ Ragdoll in August 2013, localized into 20 languages.
+</p>
+
+<p>
+ They quickly saw the impact of their decision to ship simultaneously in
+ multiple languages through increased non-English installs and improved
+ engagement with users worldwide. In addition, they started getting
+ significant usage in countries where their apps had not been as popular
+ before. They are seeing great traction in countries like Vietnam, Russia,
+ Philippines and Thailand.
+</p>
+
+<p>
+ Vivek Dave, founder of RV AppStudios, credits the success of Zombie Ragdoll
+ to localization:
+</p>
+
+<p>
+ "The value of localization is clear, it helps discoverability and helps
+ connect with the users in other countries. So when the localization
+ opportunity arose, we immediately jumped on it. Android is worldwide, and we
+ would be severely limiting ourselves if we focused on English as the only
+ language.
+</p>
+
+<p>
+ "The App Translation Service offered in the Google Play Developer Console is
+ extremely easy to use and the pricing is very attractive. Developers with
+ limited localization experience can easily create, upload, and translate
+ their app."
+</p>
+
+
+<p>
+ RV AppStudios not only localizes the text within the game, but also localizes
+ the game assets to a specific country/culture. Dave says, “Users want a
+ personalized experience, and by offering a localized game with translation of
+ text and graphic assets, we believe users will connect at a much deeper level
+ with the game.”
+</p>
+
+
+ <div style="margin-top:8px;float:left;margin-right:24px;">
+ <img src="{@docRoot}images/distribute/zombie-ragdoll-n5-land.jpg" style="width:470px;">
+ </div>
+
+
+ <div style="margin-top:128px;">
+ <p class="img-caption"><strong>Hindi version of Zombie Ragdoll</strong>:
+ Localized screenshots and videos in the app's Google Play listing go a
+ long way toward increasing the number of installs.</p>
+ </div>
+
+ </div>
+
+</div> <!-- END STORY -->
+
+<!-- START STORY -->
+
+<div style="margin-bottom:2em;padding-top:18px;clear:both;" id="sayhichat">
+
+<h3>SayHi Chat: Install growth and user engagement<br />
+ from professional translations</h3>
+
+ <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px height:78px;
+ width: 78px;
+ float: left;
+ margin: 12px 20px 9px 20px;" src=
+ "https://lh5.ggpht.com/qiL6CF1Hktz618T3mbGrxvm_OoeheQ78FgG7zr90C2MCRiz4IDQsbKuHT4xQGiWEU8o=w124">
+
+ <div style="list-style: none;height:100%;
+ float: right;
+ border-top: 1px solid #9C0;
+ width: 220px;
+ margin: 4px 20px;padding: .5em;">
+
+ <h5>About the app</h5>
+
+ <ul>
+ <li><a href="https://play.google.com/store/apps/details?id=com.unearby.sayhi">SayHi Chat,
+ Love, Meet, Dating</a></li>
+ <li>A social app to help you find people nearby</li>
+ </ul>
+
+ <h5>Localization Results</h5>
+
+ <ul>
+ <li>120% growth in language installs for new languages added</li>
+ <li>~20% increase in revenue and ~50% increase in User Reviews in the new
+ languages</li>
+ </ul>
+
+ <div style="padding:.5em 0 0 1em;">
+ <a href="https://play.google.com/store/apps/details?id=com.unearby.sayhi">
+ <img alt="Android app on Google Play"
+ src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+ </a>
+
+ </div>
+ </div>
+
+ <div style="line-height:1.4em;">
+
+<p>
+ The SayHi Chat app started out only in Japanese, Chinese and English. It soon
+ became one of the most popular apps in Japan, Hong Kong, and Taiwan. The
+ SayHi team realized it was time to launch in more languages, as the language
+ barrier was restricting how fast SayHi could grow globally. </p>
+
+ <p>Yan Shi, senior
+ developer at SayHi, says: "We checked Google Analytics for our DAU and user
+ growth numbers in each country, we also looked at total Android and iOS users
+ in those markets before finalizing our next set of languages.
+</p>
+
+ <div style="margin-top:8px;float:left;width:270px;">
+ <img src="{@docRoot}images/distribute/hichat-n5-port.jpg" style="width:240px;margin-right:48px;">
+ </div>
+
+<p>
+ SayHi used the App Translation Service to launch in 13 additional languages
+ in August 2013 and immediately saw 120% increase in install rates. In
+ addition, they are seeing their app ranked in Top 10 apps in countries like
+ Poland and Italy.
+</p>
+
+<p>Notably, they saw steady growth in Spain after replacing their previous
+ non-professional Spanish translation with a professional one produced through
+ the App Translation Service.</p>
+
+<p>
+ Yan Shi adds, “The App Translation Service is really easy to use and
+ the completion time for translation requests is very good.”
+</p>
+
+ <div style="width:600px;margin-top:98px;padding:0;">
+ <p class="img-caption"><strong>Arabic version of SayHi Chat</strong>:
+ User engagement increased significantly with
+ the localized version.</p>
+ </div>
+
+ </div>
+</div> <!-- END STORY -->
+
+
+<div style="margin-bottom:2em;clear:both;padding-top:18px;" id="g4a"><!-- START STORY -->
+
+<h3 style="line-spacing:1.25em;">G4A Indian Rummy: Benefitting from ease-of-use and<br /> fast turnaround time</h3>
+
+ <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px height:78px;
+ width: 78px;
+ float: left;
+ margin: 12px 20px 9px 20px;" src=
+ "https://lh4.ggpht.com/IxSyQgO0LWzPRoLfCrER06-0kr6aMAa2azF7eNYB30EBZAGOLYJUZulknPockbTlDYU=w124">
+
+ <div style="list-style: none;height:100%;
+ float: right;
+ border-top: 1px solid #9C0;
+ width: 220px;
+ margin: 4px 20px;padding: .5em;">
+
+ <h5>About the app</h5>
+
+ <ul>
+ <li><a href="https://play.google.com/store/apps/details?id=org.games4all.android.games.indianrummy.prod">G4A Indian Rummy</a></li>
+ <li>A card game in which the players try to form sets and sequences of cards</li>
+ </ul>
+
+ <h5>Localization Results</h5>
+
+ <ul>
+ <li>Double the number of users in French and German languages</li>
+ <li>300% increase in user engagement with localized version</li>
+ </ul>
+
+ <div style="padding:.5em 0 0 1em;">
+ <a href="https://play.google.com/store/apps/details?id=com.rvappstudios.zombieragdoll">
+ <img alt="Android app on Google Play"
+ src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+ </a>
+ </div>
+ </div>
+
+ <div style="line-height:1.4em;">
+
+<p>
+ Games4All (G4A) is the developer of Indian Rummy and a variety of games that
+ they distribute broadly to users around the world. After noticing that
+ certain apps had become especially popular in specific countries, they
+ decided to localize those apps. Initially they used a local agency to do
+ the translation and got great results — the number of users in
+ that language increased tremendously when they released the localized
+ version.
+</p>
+
+<p>
+ Building on that success, G4A expanded their localization goals but
+ found that translation quality varied across their vendors and costs limited the
+ language/game combinations they could try. That's when G4A decided to try the
+ App Translation Service.
+</p>
+
+<p>
+ Founder Pieter Olivier says, "When we heard that the App Translation
+ Service was available in the Developer Console, we jumped at the opportunity.
+ We've now been using the App Translation Service for several months and found
+ that the cost per translation is much lower than with local companies and the
+ process is much easier."
+</p>
+
+<p>So far, G4A has translated the game Indian Rummy into five languages through
+ the App Translation Service.</p>
+
+<p>
+ Olivier continues, "The first thing we did was convert all of our texts into
+ the strings.xml format. After that using the service was extremely easy and
+ straightforward. In contrast, our previous experiences with translation
+ agencies were much more difficult: files often required extensive conversion
+ operations to make them usable, and turnaround times varied wildly.
+</p>
+
+<p>
+ "With the App Translation Service, the turnaround time is usually measured in
+ days instead of weeks that we were used to with traditional translation
+ agencies."
+</p>
+
+ <div style="margin-top:14px;float:left;margin-right:24px;">
+ <img src="{@docRoot}images/distribute/indian-rummy-n4-land.jpg" style="width:470px;">
+ </div>
+
+ <div style="margin-top:158px;">
+ <p class="img-caption"><strong>Dutch
+ version of Indian Rummy</strong>: Making slight changes to games rules based on
+ local nuances was key to success of the game.</p>
+ </div>
+
+ </div>
+
+
+
+</div> <!-- END STORY -->
\ No newline at end of file
diff --git a/docs/html/guide/topics/resources/localization.jd b/docs/html/guide/topics/resources/localization.jd
index 55c8dc42..7288aeb 100644
--- a/docs/html/guide/topics/resources/localization.jd
+++ b/docs/html/guide/topics/resources/localization.jd
@@ -19,7 +19,7 @@
<ol>
<li><a href="#resource-switching">Overview: Resource-Switching in Android</a></li>
<li><a href="#using-framework">Using Resources for Localization</a></li>
-<li><a href="#strategies">Localization Strategies</a></li>
+<li><a href="#strategies">Localization Tips</a></li>
<li><a href="#testing">Testing Localized Applications</a></li>
</ol>
@@ -304,7 +304,13 @@
For more about this, see <a
href="{@docRoot}guide/topics/resources/accessing-resources.html">Accessing Resources</a>.</p>
-<h2 id="strategies">Localization Strategies</h2>
+<h2 id="checklist">Localization Checklist</h2>
+
+<p>For a complete overview of the process of localizing and distributing an Android application,
+see the <a href="{@docRoot}distribute/googleplay/publish/localizing.html">Localization
+Checklist</a> document.</p>
+
+<h2 id="strategies">Localization Tips</h2>
<h4 id="failing2">Design your application to work in any locale</h4>
@@ -478,6 +484,4 @@
portrait orientation and see if the application will run.
-<h2 id="checklist">Localization Checklist</h2>
-<p>For an overview of the process of localizing an Android application, see the <a href="{@docRoot}distribute/googleplay/publish/localizing.html">Localization Checklist</a>.</p>
diff --git a/docs/html/images/distribute/hichat-n5-port.jpg b/docs/html/images/distribute/hichat-n5-port.jpg
new file mode 100644
index 0000000..b93e983
--- /dev/null
+++ b/docs/html/images/distribute/hichat-n5-port.jpg
Binary files differ
diff --git a/docs/html/images/distribute/indian-rummy-n4-land.jpg b/docs/html/images/distribute/indian-rummy-n4-land.jpg
new file mode 100644
index 0000000..61943fc
--- /dev/null
+++ b/docs/html/images/distribute/indian-rummy-n4-land.jpg
Binary files differ
diff --git a/docs/html/images/distribute/zombie-ragdoll-n5-land.jpg b/docs/html/images/distribute/zombie-ragdoll-n5-land.jpg
new file mode 100644
index 0000000..e2bf6b5
--- /dev/null
+++ b/docs/html/images/distribute/zombie-ragdoll-n5-land.jpg
Binary files differ
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index 88fd7df..66c3034 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -74,12 +74,14 @@
<div class="sidebox-wrapper">
<div class="sidebox">
-<h2>Join the translation pilot</h2>
-<p>Google Play is offering <a
-href="{@docRoot}distribute/googleplay/publish/localizing.html#gp-trans">
-translation services</a> as part of a pilot program. If you are interested,
-sign up for the pilot program on the APK page in your Developer Console.</p>
-</div></div>
+<h2>App Translations in Google Play</h2>
+<p>Google Play <a href="{@docRoot}distribute/googleplay/publish/localizing.html#gp-trans">App
+Translation Service</a> is available in the Developer Console to help you
+localize your app for a global user base. You can browse qualified vendors, get
+estimates, upload strings for translation, and then import the translations directly
+into your app.</p>
+</div>
+</div>
<p>ADT Translation Manager Plugin is an Android SDK Tools plugin that helps
you work with strings that you are localizing. It's designed to work
@@ -97,7 +99,8 @@
localization works instantly.</p>
<p>For more information about translation services in Google Play, see <a
-href="{@docRoot}distribute/googleplay/publish/localizing.html#gp-trans">Purchase professional translations through the Developer Console</a>.</p>
+href="{@docRoot}distribute/googleplay/publish/localizing.html#gp-trans">Purchase
+professional translations through the Developer Console</a>.</p>
<p>To install the ADT Translation Manager Plugin follow these steps:</p>
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index c184e8f..27b5451 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -137,8 +137,7 @@
mDefaultAudioVideo = new RouteInfo(mSystemCategory);
mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
- mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute(
- mDefaultAudioVideo, getAllPresentationDisplays());
+ mDefaultAudioVideo.updatePresentationDisplay();
addRouteStatic(mDefaultAudioVideo);
// This will select the active wifi display route if there is one.
@@ -311,15 +310,12 @@
}
private void updatePresentationDisplays(int changedDisplayId) {
- final Display[] displays = getAllPresentationDisplays();
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
- final RouteInfo info = mRoutes.get(i);
- Display display = choosePresentationDisplayForRoute(info, displays);
- if (display != info.mPresentationDisplay
- || (display != null && display.getDisplayId() == changedDisplayId)) {
- info.mPresentationDisplay = display;
- dispatchRoutePresentationDisplayChanged(info);
+ final RouteInfo route = mRoutes.get(i);
+ if (route.updatePresentationDisplay() || (route.mPresentationDisplay != null
+ && route.mPresentationDisplay.getDisplayId() == changedDisplayId)) {
+ dispatchRoutePresentationDisplayChanged(route);
}
}
}
@@ -480,7 +476,8 @@
route.mVolume = globalRoute.volume;
route.mVolumeMax = globalRoute.volumeMax;
route.mVolumeHandling = globalRoute.volumeHandling;
- route.mPresentationDisplay = getDisplayForGlobalRoute(globalRoute);
+ route.mPresentationDisplayId = globalRoute.presentationDisplayId;
+ route.updatePresentationDisplay();
return route;
}
@@ -532,9 +529,9 @@
changed = true;
volumeChanged = true;
}
- final Display presentationDisplay = getDisplayForGlobalRoute(globalRoute);
- if (route.mPresentationDisplay != presentationDisplay) {
- route.mPresentationDisplay = presentationDisplay;
+ if (route.mPresentationDisplayId != globalRoute.presentationDisplayId) {
+ route.mPresentationDisplayId = globalRoute.presentationDisplayId;
+ route.updatePresentationDisplay();
changed = true;
presentationDisplayChanged = true;
}
@@ -550,19 +547,6 @@
}
}
- Display getDisplayForGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
- // Ensure that the specified display is valid for presentations.
- // This check will normally disallow the default display unless it was configured
- // as a presentation display for some reason.
- if (globalRoute.presentationDisplayId >= 0) {
- Display display = mDisplayService.getDisplay(globalRoute.presentationDisplayId);
- if (display != null && display.isPublicPresentation()) {
- return display;
- }
- }
- return null;
- }
-
RouteInfo findGlobalRoute(String globalRouteId) {
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
@@ -682,6 +666,19 @@
*/
public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3;
+ /**
+ * Flag for {@link #isRouteAvailable}: Ignore the default route.
+ * <p>
+ * This flag is used to determine whether a matching non-default route is available.
+ * This constraint may be used to decide whether to offer the route chooser dialog
+ * to the user. There is no point offering the chooser if there are no
+ * non-default choices.
+ * </p>
+ *
+ * @hide Future API ported from support library. Revisit this later.
+ */
+ public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
+
// Maps application contexts
static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
@@ -732,6 +729,11 @@
return sStatic.mSystemCategory;
}
+ /** @hide */
+ public RouteInfo getSelectedRoute() {
+ return getSelectedRoute(ROUTE_TYPE_ANY);
+ }
+
/**
* Return the currently selected route for any of the given types
*
@@ -755,6 +757,38 @@
}
/**
+ * Returns true if there is a route that matches the specified types.
+ * <p>
+ * This method returns true if there are any available routes that match the types
+ * regardless of whether they are enabled or disabled. If the
+ * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
+ * the method will only consider non-default routes.
+ * </p>
+ *
+ * @param types The types to match.
+ * @param flags Flags to control the determination of whether a route may be available.
+ * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
+ * @return True if a matching route may be available.
+ *
+ * @hide Future API ported from support library. Revisit this later.
+ */
+ public boolean isRouteAvailable(int types, int flags) {
+ final int count = sStatic.mRoutes.size();
+ for (int i = 0; i < count; i++) {
+ RouteInfo route = sStatic.mRoutes.get(i);
+ if (route.matchesTypes(types)) {
+ if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0
+ || route != sStatic.mDefaultAudioVideo) {
+ return true;
+ }
+ }
+ }
+
+ // It doesn't look like we can find a matching route right now.
+ return false;
+ }
+
+ /**
* Add a callback to listen to events about specific kinds of media routes.
* If the specified callback is already registered, its registration will be updated for any
* additional route types specified.
@@ -852,7 +886,7 @@
static void selectRouteStatic(int types, RouteInfo route, boolean explicit) {
final RouteInfo oldRoute = sStatic.mSelectedRoute;
if (oldRoute == route) return;
- if ((route.getSupportedTypes() & types) == 0) {
+ if (!route.matchesTypes(types)) {
Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
typesToString(route.getSupportedTypes()) + " into route types " +
typesToString(types));
@@ -1002,7 +1036,7 @@
break;
}
}
- if (info == sStatic.mSelectedRoute) {
+ if (info.isSelected()) {
// Removing the currently selected route? Select the default before we remove it.
selectDefaultRouteStatic();
}
@@ -1312,9 +1346,7 @@
newRoute.mName = display.getFriendlyDisplayName();
newRoute.mDescription = sStatic.mResources.getText(
com.android.internal.R.string.wireless_display_route_description);
-
- newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute,
- sStatic.getAllPresentationDisplays());
+ newRoute.updatePresentationDisplay();
return newRoute;
}
@@ -1337,7 +1369,7 @@
dispatchRouteChanged(route);
}
- if (!enabled && route == sStatic.mSelectedRoute) {
+ if (!enabled && route.isSelected()) {
// Oops, no longer available. Reselect the default.
selectDefaultRouteStatic();
}
@@ -1364,27 +1396,6 @@
return null;
}
- private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) {
- if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
- if (route.mDeviceAddress != null) {
- // Find the indicated Wifi display by its address.
- for (Display display : displays) {
- if (display.getType() == Display.TYPE_WIFI
- && route.mDeviceAddress.equals(display.getAddress())) {
- return display;
- }
- }
- return null;
- }
-
- if (route == sStatic.mDefaultAudioVideo && displays.length > 0) {
- // Choose the first presentation display from the list.
- return displays[0];
- }
- }
- return null;
- }
-
/**
* Information about a media route.
*/
@@ -1405,6 +1416,7 @@
int mPlaybackStream = AudioManager.STREAM_MUSIC;
VolumeCallbackInfo mVcb;
Display mPresentationDisplay;
+ int mPresentationDisplayId = -1;
String mDeviceAddress;
boolean mEnabled = true;
@@ -1564,6 +1576,11 @@
return mSupportedTypes;
}
+ /** @hide */
+ public boolean matchesTypes(int types) {
+ return (mSupportedTypes & types) != 0;
+ }
+
/**
* @return The group that this route belongs to.
*/
@@ -1743,6 +1760,50 @@
return mPresentationDisplay;
}
+ boolean updatePresentationDisplay() {
+ Display display = choosePresentationDisplay();
+ if (mPresentationDisplay != display) {
+ mPresentationDisplay = display;
+ return true;
+ }
+ return false;
+ }
+
+ private Display choosePresentationDisplay() {
+ if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
+ Display[] displays = sStatic.getAllPresentationDisplays();
+
+ // Ensure that the specified display is valid for presentations.
+ // This check will normally disallow the default display unless it was
+ // configured as a presentation display for some reason.
+ if (mPresentationDisplayId >= 0) {
+ for (Display display : displays) {
+ if (display.getDisplayId() == mPresentationDisplayId) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ // Find the indicated Wifi display by its address.
+ if (mDeviceAddress != null) {
+ for (Display display : displays) {
+ if (display.getType() == Display.TYPE_WIFI
+ && mDeviceAddress.equals(display.getAddress())) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ // For the default route, choose the first presentation display from the list.
+ if (this == sStatic.mDefaultAudioVideo && displays.length > 0) {
+ return displays[0];
+ }
+ }
+ return null;
+ }
+
/**
* Returns true if this route is enabled and may be selected.
*
@@ -1763,7 +1824,7 @@
// then report it as connecting even though it has not yet had a chance
// to move into the CONNECTING state. Note that routes in the NONE state
// are assumed to not require an explicit connection lifecycle.
- if (this == sStatic.mSelectedRoute) {
+ if (isSelected()) {
switch (mStatusCode) {
case STATUS_AVAILABLE:
case STATUS_SCANNING:
@@ -1774,6 +1835,21 @@
return false;
}
+ /** @hide */
+ public boolean isSelected() {
+ return this == sStatic.mSelectedRoute;
+ }
+
+ /** @hide */
+ public boolean isDefault() {
+ return this == sStatic.mDefaultAudioVideo;
+ }
+
+ /** @hide */
+ public void select() {
+ selectRouteStatic(mSupportedTypes, this, true);
+ }
+
void setStatusInt(CharSequence status) {
if (!status.equals(mStatus)) {
mStatus = status;
diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java
index 8e4042c..701c39c 100644
--- a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java
+++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java
@@ -257,6 +257,15 @@
publishState();
}
+ /**
+ * Finds the remote display with the specified id, returns null if not found.
+ *
+ * @param id Id of the remote display.
+ */
+ public RemoteDisplay findRemoteDisplay(String id) {
+ return mDisplays.get(id);
+ }
+
void setCallback(IRemoteDisplayCallback callback) {
mCallback = callback;
publishState();
@@ -285,10 +294,6 @@
}
}
- RemoteDisplay findRemoteDisplay(String id) {
- return mDisplays.get(id);
- }
-
final class ProviderStub extends IRemoteDisplayProvider.Stub {
@Override
public void setCallback(IRemoteDisplayCallback callback) {
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png
deleted file mode 100644
index 02d7fda..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index 263f07c..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png
deleted file mode 100644
index 09ae409..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index 780cfc8..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png
deleted file mode 100644
index 48f90ac..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index 621c045..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png
deleted file mode 100644
index b07be828..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
deleted file mode 100644
index f02d0ab..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index bb37837..e6fd7d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -38,8 +38,8 @@
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LevelListDrawable;
import android.hardware.display.DisplayManager;
+import android.media.MediaRouter;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Handler;
@@ -62,6 +62,7 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.app.MediaRouteDialogPresenter;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState;
import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState;
@@ -704,11 +705,24 @@
// Remote Display
QuickSettingsBasicTile remoteDisplayTile
= new QuickSettingsBasicTile(mContext);
- remoteDisplayTile.setImageResource(R.drawable.ic_qs_remote_display);
remoteDisplayTile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+ collapsePanels();
+
+ final Dialog[] dialog = new Dialog[1];
+ dialog[0] = MediaRouteDialogPresenter.createDialog(mContext,
+ MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog[0].dismiss();
+ startSettingsActivity(
+ android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+ }
+ });
+ dialog[0].getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ dialog[0].show();
}
});
mModel.addRemoteDisplayTile(remoteDisplayTile,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index d8950f4..48523c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -806,14 +806,18 @@
private void updateRemoteDisplays() {
MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute(
MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
- boolean enabled = connectedRoute != null && (connectedRoute.getSupportedTypes()
- & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0;
- if (!enabled) {
+ boolean enabled = connectedRoute != null
+ && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+ boolean connecting;
+ if (enabled) {
+ connecting = connectedRoute.isConnecting();
+ } else {
connectedRoute = null;
+ connecting = false;
final int count = mMediaRouter.getRouteCount();
for (int i = 0; i < count; i++) {
MediaRouter.RouteInfo route = mMediaRouter.getRouteAt(i);
- if ((route.getSupportedTypes() & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+ if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) {
enabled = true;
break;
}
@@ -823,11 +827,14 @@
mRemoteDisplayState.enabled = enabled;
if (connectedRoute != null) {
mRemoteDisplayState.label = connectedRoute.getName().toString();
- mRemoteDisplayState.iconId = R.drawable.ic_qs_remote_display_connected;
+ mRemoteDisplayState.iconId = connecting ?
+ com.android.internal.R.drawable.ic_media_route_connecting_holo_dark :
+ com.android.internal.R.drawable.ic_media_route_on_holo_dark;
} else {
mRemoteDisplayState.label = mContext.getString(
R.string.quick_settings_remote_display_no_connection_label);
- mRemoteDisplayState.iconId = R.drawable.ic_qs_remote_display;
+ mRemoteDisplayState.iconId =
+ com.android.internal.R.drawable.ic_media_route_off_holo_dark;
}
mRemoteDisplayCallback.refreshView(mRemoteDisplayTile, mRemoteDisplayState);
}