Merge "NumberPicker should adjust min and max when displayed values are set." into jb-mr1-dev
diff --git a/Android.mk b/Android.mk
index be98487..d38150f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -429,6 +429,7 @@
-since ./frameworks/base/api/14.txt 14 \
-since ./frameworks/base/api/15.txt 15 \
-since ./frameworks/base/api/16.txt 16 \
+ -since ./frameworks/base/api/17.txt 17 \
-werror -hide 113 \
-overview $(LOCAL_PATH)/core/java/overview.html
@@ -539,7 +540,7 @@
## SDK version identifiers used in the published docs
# major[.minor] version for current SDK. (full releases only)
-framework_docs_SDK_VERSION:=4.1
+framework_docs_SDK_VERSION:=4.2
# release version (ie "Release x") (full releases only)
framework_docs_SDK_REL_ID:=1
diff --git a/api/current.txt b/api/current.txt
index e8f911b..ab45790 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11750,6 +11750,7 @@
method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
+ field public static final int ROUTE_TYPE_LIVE_VIDEO = 2; // 0x2
field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
}
@@ -11798,6 +11799,7 @@
method public int getVolume();
method public int getVolumeHandling();
method public int getVolumeMax();
+ method public boolean isEnabled();
method public void requestSetVolume(int);
method public void requestUpdateVolume(int);
method public void setTag(java.lang.Object);
@@ -25040,7 +25042,6 @@
method public boolean isInEditMode();
method public boolean isInTouchMode();
method public boolean isLayoutRequested();
- method public boolean isLayoutRtl();
method public boolean isLongClickable();
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
@@ -25678,7 +25679,6 @@
method public int getLayoutDirection();
method public int getMarginEnd();
method public int getMarginStart();
- method protected boolean isLayoutRtl();
method public boolean isMarginRelative();
method public void setLayoutDirection(int);
method public void setMarginEnd(int);
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index cfc8bbd..a9ccef0 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -221,21 +221,28 @@
void updateRouteCount() {
final int N = mRouter.getRouteCount();
int count = 0;
+ boolean hasVideoRoutes = false;
for (int i = 0; i < N; i++) {
final RouteInfo route = mRouter.getRouteAt(i);
- if ((route.getSupportedTypes() & mRouteTypes) != 0) {
+ 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) != 0) {
+ hasVideoRoutes = true;
+ }
}
}
setEnabled(count != 0);
- // Only allow toggling if we have more than just user routes
- mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0;
+ // Only allow toggling if we have more than just user routes.
+ // Don't toggle if we support video routes, we may have to let the dialog scan.
+ mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
+ !hasVideoRoutes;
}
@Override
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b3ab385..5b49ba3 100755
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -7659,6 +7659,54 @@
public static final int MODE_LARGE = 3;
/**
+ * Constructs the QuickContacts intent with a view's rect.
+ * @hide
+ */
+ public static Intent composeQuickContactsIntent(Context context, View target, Uri lookupUri,
+ int mode, String[] excludeMimes) {
+ // Find location and bounds of target view, adjusting based on the
+ // assumed local density.
+ final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
+ target.getLocationOnScreen(pos);
+
+ final Rect rect = new Rect();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
+
+ return composeQuickContactsIntent(context, rect, lookupUri, mode, excludeMimes);
+ }
+
+ /**
+ * Constructs the QuickContacts intent.
+ * @hide
+ */
+ public static Intent composeQuickContactsIntent(Context context, Rect target,
+ Uri lookupUri, int mode, String[] excludeMimes) {
+ // When launching from an Activiy, we don't want to start a new task, but otherwise
+ // we *must* start a new task. (Otherwise startActivity() would crash.)
+ Context actualContext = context;
+ while ((actualContext instanceof ContextWrapper)
+ && !(actualContext instanceof Activity)) {
+ actualContext = ((ContextWrapper) actualContext).getBaseContext();
+ }
+ final int intentFlags = (actualContext instanceof Activity)
+ ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+ : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
+
+ // Launch pivot dialog through intent for now
+ final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
+
+ intent.setData(lookupUri);
+ intent.setSourceBounds(target);
+ intent.putExtra(EXTRA_MODE, mode);
+ intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
+ return intent;
+ }
+
+ /**
* Trigger a dialog that lists the various methods of interacting with
* the requested {@link Contacts} entry. This may be based on available
* {@link ContactsContract.Data} rows under that contact, and may also
@@ -7683,20 +7731,10 @@
*/
public static void showQuickContact(Context context, View target, Uri lookupUri, int mode,
String[] excludeMimes) {
- // Find location and bounds of target view, adjusting based on the
- // assumed local density.
- final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
- final int[] pos = new int[2];
- target.getLocationOnScreen(pos);
-
- final Rect rect = new Rect();
- rect.left = (int) (pos[0] * appScale + 0.5f);
- rect.top = (int) (pos[1] * appScale + 0.5f);
- rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
- rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
-
// Trigger with obtained rectangle
- showQuickContact(context, rect, lookupUri, mode, excludeMimes);
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
+ excludeMimes);
+ context.startActivity(intent);
}
/**
@@ -7727,25 +7765,9 @@
*/
public static void showQuickContact(Context context, Rect target, Uri lookupUri, int mode,
String[] excludeMimes) {
- // When launching from an Activiy, we don't want to start a new task, but otherwise
- // we *must* start a new task. (Otherwise startActivity() would crash.)
- Context actualContext = context;
- while ((actualContext instanceof ContextWrapper)
- && !(actualContext instanceof Activity)) {
- actualContext = ((ContextWrapper) actualContext).getBaseContext();
- }
- final int intentFlags = (actualContext instanceof Activity)
- ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
- : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
-
- // Launch pivot dialog through intent for now
- final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
-
- intent.setData(lookupUri);
- intent.setSourceBounds(target);
- intent.putExtra(EXTRA_MODE, mode);
- intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
- context.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
+ excludeMimes);
+ context.startActivity(intent);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 550713d..056103b0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3141,6 +3141,12 @@
public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED;
/**
+ * Whether the current user has been set up via setup wizard (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String USER_SETUP_COMPLETE = "user_setup_complete";
+
+ /**
* List of input methods that are currently enabled. This is a string
* containing the IDs of all enabled input methods, each ID separated
* by ':'.
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index b0a2711..4873860 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.SystemClock;
import android.util.FloatMath;
+import android.util.Log;
import java.util.Arrays;
@@ -223,10 +224,14 @@
* @param id pointer id to clear
* @see #addTouchHistory(MotionEvent)
*/
- private void removeTouchHistoryForId(int id) {
+ private boolean removeTouchHistoryForId(int id) {
+ if (id >= mTouchHistoryLastAccepted.length) {
+ return false;
+ }
mTouchHistoryLastAccepted[id] = Float.NaN;
mTouchHistoryDirection[id] = 0;
mTouchHistoryLastAcceptedTime[id] = 0;
+ return true;
}
/**
@@ -236,6 +241,11 @@
* @see #addTouchHistory(MotionEvent)
*/
private float getAdjustedTouchHistory(int id) {
+ if (id >= mTouchHistoryLastAccepted.length) {
+ Log.e(TAG, "Error retrieving adjusted touch history for id=" + id +
+ " - incomplete event stream?");
+ return 0;
+ }
return mTouchHistoryLastAccepted[id];
}
@@ -244,6 +254,10 @@
* @see #addTouchHistory(MotionEvent)
*/
private void clearTouchHistory() {
+ if (mTouchHistoryLastAccepted == null) {
+ // All three arrays will be null if this is the case; nothing to do.
+ return;
+ }
Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
Arrays.fill(mTouchHistoryDirection, 0);
Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
@@ -333,7 +347,11 @@
final float focusY = sumY / div;
if (pointerUp) {
- removeTouchHistoryForId(event.getPointerId(event.getActionIndex()));
+ final int id = event.getPointerId(event.getActionIndex());
+ if (!removeTouchHistoryForId(id)) {
+ Log.e(TAG, "Got ACTION_POINTER_UP for previously unknown id=" + id +
+ " - incomplete event stream?");
+ }
} else {
addTouchHistory(event);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 111f959..f9ff865 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5887,6 +5887,8 @@
* layout attribute and/or the inherited value from the parent
*
* @return true if the layout is right-to-left.
+ *
+ * @hide
*/
@ViewDebug.ExportedProperty(category = "layout")
public boolean isLayoutRtl() {
@@ -11628,9 +11630,11 @@
* Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
* that the parent directionality can and will be resolved before its children.
*
+ * @return true if resolution has been done, false otherwise.
+ *
* @hide
*/
- public void resolveLayoutDirection() {
+ public boolean resolveLayoutDirection() {
// Clear any previous layout direction resolution
mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
@@ -11641,15 +11645,13 @@
case LAYOUT_DIRECTION_INHERIT:
// We cannot resolve yet. LTR is by default and let the resolution happen again
// later to get the correct resolved value
- if (!canResolveLayoutDirection()) return;
+ if (!canResolveLayoutDirection()) return false;
- ViewGroup viewGroup = ((ViewGroup) mParent);
+ View parent = ((View) mParent);
+ // Parent has not yet resolved, LTR is still the default
+ if (!parent.isLayoutDirectionResolved()) return false;
- // We cannot resolve yet on the parent too. LTR is by default and let the
- // resolution happen again later
- if (!viewGroup.canResolveLayoutDirection()) return;
-
- if (viewGroup.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
}
break;
@@ -11669,6 +11671,7 @@
// Set to resolved
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ return true;
}
/**
@@ -11679,10 +11682,10 @@
* @hide
*/
public boolean canResolveLayoutDirection() {
- switch ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >>
- PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) {
+ switch (getRawLayoutDirection()) {
case LAYOUT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof ViewGroup);
+ return (mParent != null) && (mParent instanceof ViewGroup) &&
+ ((ViewGroup) mParent).canResolveLayoutDirection();
default:
return true;
}
@@ -16640,9 +16643,11 @@
/**
* Resolve the text direction.
*
+ * @return true if resolution has been done, false otherwise.
+ *
* @hide
*/
- public void resolveTextDirection() {
+ public boolean resolveTextDirection() {
// Reset any previous text direction resolution
mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
@@ -16651,29 +16656,35 @@
final int textDirection = getRawTextDirection();
switch(textDirection) {
case TEXT_DIRECTION_INHERIT:
- if (canResolveTextDirection()) {
- ViewGroup viewGroup = ((ViewGroup) mParent);
-
- // Set current resolved direction to the same value as the parent's one
- final int parentResolvedDirection = viewGroup.getTextDirection();
- switch (parentResolvedDirection) {
- case TEXT_DIRECTION_FIRST_STRONG:
- case TEXT_DIRECTION_ANY_RTL:
- case TEXT_DIRECTION_LTR:
- case TEXT_DIRECTION_RTL:
- case TEXT_DIRECTION_LOCALE:
- mPrivateFlags2 |=
- (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
- break;
- default:
- // Default resolved direction is "first strong" heuristic
- mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
- }
- } else {
+ if (!canResolveTextDirection()) {
// We cannot do the resolution if there is no parent, so use the default one
mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
// Resolution will need to happen again later
- return;
+ return false;
+ }
+
+ View parent = ((View) mParent);
+ // Parent has not yet resolved, so we still return the default
+ if (!parent.isTextDirectionResolved()) {
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ // Set current resolved direction to the same value as the parent's one
+ final int parentResolvedDirection = parent.getTextDirection();
+ switch (parentResolvedDirection) {
+ case TEXT_DIRECTION_FIRST_STRONG:
+ case TEXT_DIRECTION_ANY_RTL:
+ case TEXT_DIRECTION_LTR:
+ case TEXT_DIRECTION_RTL:
+ case TEXT_DIRECTION_LOCALE:
+ mPrivateFlags2 |=
+ (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Default resolved direction is "first strong" heuristic
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
}
break;
case TEXT_DIRECTION_FIRST_STRONG:
@@ -16695,6 +16706,7 @@
// Set to resolved
mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED;
+ return true;
}
/**
@@ -16705,7 +16717,8 @@
private boolean canResolveTextDirection() {
switch (getRawTextDirection()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof ViewGroup);
+ return (mParent != null) && (mParent instanceof View) &&
+ ((View) mParent).canResolveTextDirection();
default:
return true;
}
@@ -16835,9 +16848,11 @@
/**
* Resolve the text alignment.
*
+ * @return true if resolution has been done, false otherwise.
+ *
* @hide
*/
- public void resolveTextAlignment() {
+ public boolean resolveTextAlignment() {
// Reset any previous text alignment resolution
mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
@@ -16847,32 +16862,37 @@
switch (textAlignment) {
case TEXT_ALIGNMENT_INHERIT:
// Check if we can resolve the text alignment
- if (canResolveTextAlignment() && mParent instanceof View) {
- View view = (View) mParent;
-
- final int parentResolvedTextAlignment = view.getTextAlignment();
- switch (parentResolvedTextAlignment) {
- case TEXT_ALIGNMENT_GRAVITY:
- case TEXT_ALIGNMENT_TEXT_START:
- case TEXT_ALIGNMENT_TEXT_END:
- case TEXT_ALIGNMENT_CENTER:
- case TEXT_ALIGNMENT_VIEW_START:
- case TEXT_ALIGNMENT_VIEW_END:
- // Resolved text alignment is the same as the parent resolved
- // text alignment
- mPrivateFlags2 |=
- (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
- break;
- default:
- // Use default resolved text alignment
- mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
- }
- }
- else {
+ if (!canResolveTextAlignment()) {
// We cannot do the resolution if there is no parent so use the default
mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
// Resolution will need to happen again later
- return;
+ return false;
+ }
+ View parent = (View) mParent;
+
+ // Parent has not yet resolved, so we still return the default
+ if (!parent.isTextAlignmentResolved()) {
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ final int parentResolvedTextAlignment = parent.getTextAlignment();
+ switch (parentResolvedTextAlignment) {
+ case TEXT_ALIGNMENT_GRAVITY:
+ case TEXT_ALIGNMENT_TEXT_START:
+ case TEXT_ALIGNMENT_TEXT_END:
+ case TEXT_ALIGNMENT_CENTER:
+ case TEXT_ALIGNMENT_VIEW_START:
+ case TEXT_ALIGNMENT_VIEW_END:
+ // Resolved text alignment is the same as the parent resolved
+ // text alignment
+ mPrivateFlags2 |=
+ (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Use default resolved text alignment
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
break;
case TEXT_ALIGNMENT_GRAVITY:
@@ -16895,6 +16915,7 @@
// Set the resolved
mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+ return true;
}
/**
@@ -16905,7 +16926,8 @@
private boolean canResolveTextAlignment() {
switch (getRawTextAlignment()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null);
+ return (mParent != null) && (mParent instanceof View) &&
+ ((View) mParent).canResolveTextAlignment();
default:
return true;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 41890d6..db1c00a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3382,6 +3382,11 @@
ai.mKeepScreenOn = lastKeepOn;
}
+ if (child.isLayoutDirectionInherited()) {
+ child.resetResolvedLayoutDirection();
+ child.resolveRtlPropertiesIfNeeded();
+ }
+
onViewAdded(child);
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
@@ -5256,48 +5261,54 @@
* @hide
*/
@Override
- public void resolveLayoutDirection() {
- super.resolveLayoutDirection();
-
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.isLayoutDirectionInherited()) {
- child.resolveLayoutDirection();
+ public boolean resolveLayoutDirection() {
+ final boolean result = super.resolveLayoutDirection();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resolveLayoutDirection();
+ }
}
}
+ return result;
}
/**
* @hide
*/
@Override
- public void resolveTextDirection() {
- super.resolveTextDirection();
-
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.isTextDirectionInherited()) {
- child.resolveTextDirection();
+ public boolean resolveTextDirection() {
+ final boolean result = super.resolveTextDirection();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextDirectionInherited()) {
+ child.resolveTextDirection();
+ }
}
}
+ return result;
}
/**
* @hide
*/
@Override
- public void resolveTextAlignment() {
- super.resolveTextAlignment();
-
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.isTextAlignmentInherited()) {
- child.resolveTextAlignment();
+ public boolean resolveTextAlignment() {
+ final boolean result = super.resolveTextAlignment();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextAlignmentInherited()) {
+ child.resolveTextAlignment();
+ }
}
}
+ return result;
}
/**
@@ -5893,7 +5904,10 @@
}
}
- protected boolean isLayoutRtl() {
+ /**
+ * @hide
+ */
+ public boolean isLayoutRtl() {
return (layoutDirection == View.LAYOUT_DIRECTION_RTL);
}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index f010d7b..386f387 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -25,7 +25,7 @@
import android.app.MediaRouteButton;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
+import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteCategory;
import android.media.MediaRouter.RouteGroup;
@@ -70,6 +70,7 @@
};
MediaRouter mRouter;
+ DisplayManager mDisplayService;
private int mRouteTypes;
private LayoutInflater mInflater;
@@ -97,6 +98,7 @@
public void onAttach(Activity activity) {
super.onAttach(activity);
mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ mDisplayService = (DisplayManager) activity.getSystemService(Context.DISPLAY_SERVICE);
}
@Override
@@ -119,6 +121,15 @@
public void setRouteTypes(int types) {
mRouteTypes = types;
+ if ((mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0 && mDisplayService == null) {
+ final Context activity = getActivity();
+ if (activity != null) {
+ mDisplayService = (DisplayManager) activity.getSystemService(
+ Context.DISPLAY_SERVICE);
+ }
+ } else {
+ mDisplayService = null;
+ }
}
void updateVolume() {
@@ -194,6 +205,9 @@
@Override
public void onResume() {
super.onResume();
+ if (mDisplayService != null) {
+ mDisplayService.scanWifiDisplays();
+ }
}
private static class ViewHolder {
@@ -253,7 +267,9 @@
final RouteCategory cat = mRouter.getCategoryAt(i);
routes = cat.getRoutes(mCatRouteList);
- mItems.add(cat);
+ if (!cat.isSystem()) {
+ mItems.add(cat);
+ }
if (cat == mCategoryEditingGroups) {
addGroupEditingCategoryRoutes(routes);
@@ -370,6 +386,7 @@
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;
@@ -434,6 +451,7 @@
}
convertView.setActivated(position == mSelectedItemPosition);
+ convertView.setEnabled(isEnabled(position));
return convertView;
}
diff --git a/core/res/res/drawable-hdpi/magnified_region_frame.9.png b/core/res/res/drawable-hdpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..29bdc42
--- /dev/null
+++ b/core/res/res/drawable-hdpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/magnified_region_frame.9.png b/core/res/res/drawable-mdpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..a61cbea
--- /dev/null
+++ b/core/res/res/drawable-mdpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/magnified_region_frame.9.png b/core/res/res/drawable-nodpi/magnified_region_frame.9.png
deleted file mode 100644
index 4cadefb..0000000
--- a/core/res/res/drawable-nodpi/magnified_region_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/magnified_region_frame.9.png b/core/res/res/drawable-xhdpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..424b3d9
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index 53d813e..423d544 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -24,7 +24,8 @@
android:layout_height="56dp"
android:scaleType="center"
android:id="@+id/icon"
- android:visibility="gone" />
+ android:visibility="gone"
+ android:duplicateParentState="true" />
<LinearLayout android:layout_width="0dp"
android:layout_height="match_parent"
@@ -32,21 +33,24 @@
android:orientation="vertical"
android:gravity="start|center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:duplicateParentState="true">
<TextView android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceMedium" />
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:duplicateParentState="true" />
<TextView android:id="@android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall" />
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:duplicateParentState="true" />
</LinearLayout>
<ImageButton
@@ -56,6 +60,7 @@
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_media_group_expand"
android:scaleType="center"
- android:visibility="gone" />
+ android:visibility="gone"
+ android:duplicateParentState="true" />
</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 02aa537..1b56c29 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3768,8 +3768,8 @@
<!-- Name of the default audio route when an audio dock is connected. [CHAR LIMIT=50] -->
<string name="default_audio_route_name_dock_speakers">Dock speakers</string>
- <!-- Name of the default audio route when HDMI is connected. [CHAR LIMIT=50] -->
- <string name="default_audio_route_name_hdmi">HDMI audio</string>
+ <!-- Name of the default media route when HDMI is connected. [CHAR LIMIT=50] -->
+ <string name="default_media_route_name_hdmi">HDMI</string>
<!-- Name of the default audio route category. [CHAR LIMIT=50] -->
<string name="default_audio_route_category_name">System</string>
@@ -3783,6 +3783,18 @@
<!-- Content description of a MediaRouteButton for accessibility support -->
<string name="media_route_button_content_description">Media output</string>
+ <!-- Status message for remote routes attempting to scan/determine availability -->
+ <string name="media_route_status_scanning">Scanning...</string>
+
+ <!-- Status message for a remote route attempting to connect -->
+ <string name="media_route_status_connecting">Connecting...</string>
+
+ <!-- Status message for a remote route that is confirmed to be available for connection -->
+ <string name="media_route_status_available">Available</string>
+
+ <!-- Status message for remote routes that are not available for connection right now -->
+ <string name="media_route_status_not_available">Not available</string>
+
<!-- Display manager service -->
<!-- Name of the built-in display. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a4136b..e16dd33 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -823,9 +823,13 @@
<java-symbol type="string" name="default_audio_route_name" />
<java-symbol type="string" name="default_audio_route_name_headphones" />
<java-symbol type="string" name="default_audio_route_name_dock_speakers" />
- <java-symbol type="string" name="default_audio_route_name_hdmi" />
+ <java-symbol type="string" name="default_media_route_name_hdmi" />
<java-symbol type="string" name="default_audio_route_category_name" />
<java-symbol type="string" name="safe_media_volume_warning" />
+ <java-symbol type="string" name="media_route_status_scanning" />
+ <java-symbol type="string" name="media_route_status_connecting" />
+ <java-symbol type="string" name="media_route_status_available" />
+ <java-symbol type="string" name="media_route_status_not_available" />
<java-symbol type="plurals" name="abbrev_in_num_days" />
<java-symbol type="plurals" name="abbrev_in_num_hours" />
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index a256079..36c9c70 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -22,12 +22,17 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
import java.util.ArrayList;
import java.util.HashMap;
@@ -51,6 +56,7 @@
static class Static {
final Resources mResources;
final IAudioService mAudioService;
+ final DisplayManager mDisplayService;
final Handler mHandler;
final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
new CopyOnWriteArrayList<CallbackInfo>();
@@ -60,18 +66,20 @@
final RouteCategory mSystemCategory;
- final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo();
+ final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
- RouteInfo mDefaultAudio;
+ RouteInfo mDefaultAudioVideo;
RouteInfo mBluetoothA2dpRoute;
RouteInfo mSelectedRoute;
- final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() {
+ WifiDisplayStatus mLastKnownWifiDisplayStatus;
+
+ final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
mHandler.post(new Runnable() {
@Override public void run() {
- updateRoutes(newRoutes);
+ updateAudioRoutes(newRoutes);
}
});
}
@@ -84,34 +92,42 @@
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
mAudioService = IAudioService.Stub.asInterface(b);
+ mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
+
mSystemCategory = new RouteCategory(
com.android.internal.R.string.default_audio_route_category_name,
- ROUTE_TYPE_LIVE_AUDIO, false);
+ ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
+ mSystemCategory.mIsSystem = true;
}
// Called after sStatic is initialized
void startMonitoringRoutes(Context appContext) {
- mDefaultAudio = new RouteInfo(mSystemCategory);
- mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
- mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
- addRoute(mDefaultAudio);
+ 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;
+ addRoute(mDefaultAudioVideo);
appContext.registerReceiver(new VolumeChangeReceiver(),
new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
- AudioRoutesInfo newRoutes = null;
+ AudioRoutesInfo newAudioRoutes = null;
try {
- newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver);
+ newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
} catch (RemoteException e) {
}
- if (newRoutes != null) {
- updateRoutes(newRoutes);
+ if (newAudioRoutes != null) {
+ updateAudioRoutes(newAudioRoutes);
}
+
+ updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
+
+ appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
+ new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
}
- void updateRoutes(AudioRoutesInfo newRoutes) {
- if (newRoutes.mMainType != mCurRoutesInfo.mMainType) {
- mCurRoutesInfo.mMainType = newRoutes.mMainType;
+ void updateAudioRoutes(AudioRoutesInfo newRoutes) {
+ if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
+ mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
int name;
if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
|| (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
@@ -119,12 +135,12 @@
} else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
- name = com.android.internal.R.string.default_audio_route_name_hdmi;
+ name = com.android.internal.R.string.default_media_route_name_hdmi;
} else {
name = com.android.internal.R.string.default_audio_route_name;
}
- sStatic.mDefaultAudio.mNameResId = name;
- dispatchRouteChanged(sStatic.mDefaultAudio);
+ sStatic.mDefaultAudioVideo.mNameResId = name;
+ dispatchRouteChanged(sStatic.mDefaultAudioVideo);
}
boolean a2dpEnabled;
@@ -135,17 +151,17 @@
a2dpEnabled = false;
}
- if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) {
- mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
- if (mCurRoutesInfo.mBluetoothName != null) {
+ if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
+ mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
+ if (mCurAudioRoutesInfo.mBluetoothName != null) {
if (sStatic.mBluetoothA2dpRoute == null) {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
- info.mName = mCurRoutesInfo.mBluetoothName;
+ info.mName = mCurAudioRoutesInfo.mBluetoothName;
info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
sStatic.mBluetoothA2dpRoute = info;
addRoute(sStatic.mBluetoothA2dpRoute);
} else {
- sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName;
+ sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
}
} else if (sStatic.mBluetoothA2dpRoute != null) {
@@ -155,11 +171,11 @@
}
if (mBluetoothA2dpRoute != null) {
- if (mCurRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER &&
+ if (mCurAudioRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER &&
mSelectedRoute == mBluetoothA2dpRoute) {
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudio);
- } else if (mCurRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER &&
- mSelectedRoute == mDefaultAudio && a2dpEnabled) {
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+ } else if (mCurAudioRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER &&
+ mSelectedRoute == mDefaultAudioVideo && a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
}
}
@@ -181,6 +197,20 @@
public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
/**
+ * Route type flag for live video.
+ *
+ * <p>A device that supports live video routing will allow a mirrored version
+ * of the device's primary display or a customized
+ * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
+ *
+ * <p>Once initiated, display mirroring is transparent to the application.
+ * While remote routing is active the application may use a
+ * {@link android.app.Presentation Presentation} to replace the mirrored view
+ * on the external display with different content.</p>
+ */
+ public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
+
+ /**
* Route type flag for application-specific usage.
*
* <p>Unlike other media route types, user routes are managed by the application.
@@ -219,7 +249,7 @@
* @hide for use by framework routing UI
*/
public RouteInfo getSystemAudioRoute() {
- return sStatic.mDefaultAudio;
+ return sStatic.mDefaultAudioVideo;
}
/**
@@ -296,7 +326,8 @@
}
static void selectRouteStatic(int types, RouteInfo route) {
- if (sStatic.mSelectedRoute == route) return;
+ final RouteInfo oldRoute = sStatic.mSelectedRoute;
+ if (oldRoute == route) return;
if ((route.getSupportedTypes() & types) == 0) {
Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
typesToString(route.getSupportedTypes()) + " into route types " +
@@ -306,7 +337,7 @@
final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
- (route == btRoute || route == sStatic.mDefaultAudio)) {
+ (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
try {
sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
} catch (RemoteException e) {
@@ -314,10 +345,21 @@
}
}
- if (sStatic.mSelectedRoute != null) {
+ final WifiDisplay activeDisplay =
+ sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
+ final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
+ final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
+ if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
+ if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
+ sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
+ } else if (activeDisplay != null && !newRouteHasAddress) {
+ sStatic.mDisplayService.disconnectWifiDisplay();
+ }
+ }
+
+ if (oldRoute != null) {
// TODO filter types properly
- dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(),
- sStatic.mSelectedRoute);
+ dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
}
sStatic.mSelectedRoute = route;
if (route != null) {
@@ -327,6 +369,22 @@
}
/**
+ * Compare the device address of a display and a route.
+ * Nulls/no device address will match another null/no address.
+ */
+ static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
+ final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
+ if (display == null && !routeHasAddress) {
+ return true;
+ }
+
+ if (display != null && routeHasAddress) {
+ return display.getDeviceAddress().equals(info.mDeviceAddress);
+ }
+ return false;
+ }
+
+ /**
* Add an app-specified route for media to the MediaRouter.
* App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
*
@@ -419,7 +477,7 @@
if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid.
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo);
}
if (!found) {
sStatic.mCategories.remove(removingCat);
@@ -444,7 +502,8 @@
if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid.
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
+ sStatic.mDefaultAudioVideo);
}
if (!found) {
sStatic.mCategories.remove(removingCat);
@@ -611,20 +670,151 @@
if (selectedRoute == null) return;
if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
- selectedRoute == sStatic.mDefaultAudio) {
+ selectedRoute == sStatic.mDefaultAudioVideo) {
dispatchRouteVolumeChanged(selectedRoute);
} else if (sStatic.mBluetoothA2dpRoute != null) {
try {
dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
- sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio);
+ sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
} catch (RemoteException e) {
Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
}
} else {
- dispatchRouteVolumeChanged(sStatic.mDefaultAudio);
+ dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
}
}
+ static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
+ final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
+
+ // TODO Naive implementation. Make this smarter later.
+ boolean needScan = false;
+ WifiDisplay[] oldDisplays = oldStatus != null ?
+ oldStatus.getRememberedDisplays() : new WifiDisplay[0];
+ WifiDisplay[] newDisplays = newStatus.getRememberedDisplays();
+ WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays();
+
+ for (int i = 0; i < newDisplays.length; i++) {
+ final WifiDisplay d = newDisplays[i];
+ final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays);
+ if (oldRemembered == null) {
+ addRoute(makeWifiDisplayRoute(d));
+ needScan = true;
+ } else {
+ final boolean available = findMatchingDisplay(d, availableDisplays) != null;
+ final RouteInfo route = findWifiDisplayRoute(d);
+ updateWifiDisplayRoute(route, d, available, newStatus);
+ }
+ }
+ for (int i = 0; i < oldDisplays.length; i++) {
+ final WifiDisplay d = oldDisplays[i];
+ final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
+ if (newDisplay == null) {
+ removeRoute(findWifiDisplayRoute(d));
+ }
+ }
+
+ if (needScan) {
+ sStatic.mDisplayService.scanWifiDisplays();
+ }
+
+ sStatic.mLastKnownWifiDisplayStatus = newStatus;
+ }
+
+ static RouteInfo makeWifiDisplayRoute(WifiDisplay display) {
+ final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
+ newRoute.mDeviceAddress = display.getDeviceAddress();
+ newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
+ newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
+ newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
+ newRoute.mStatus = sStatic.mResources.getText(
+ com.android.internal.R.string.media_route_status_connecting);
+ newRoute.mEnabled = false;
+
+ newRoute.mName = makeWifiDisplayName(display);
+ return newRoute;
+ }
+
+ static String makeWifiDisplayName(WifiDisplay display) {
+ String name = display.getDeviceAlias();
+ if (TextUtils.isEmpty(name)) {
+ name = display.getDeviceName();
+ }
+ return name;
+ }
+
+ private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display,
+ boolean available, WifiDisplayStatus wifiDisplayStatus) {
+ final boolean isScanning =
+ wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING;
+
+ boolean changed = false;
+ int newStatus = RouteInfo.STATUS_NONE;
+
+ if (available) {
+ newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE;
+ } else {
+ newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
+ }
+
+ if (display.equals(wifiDisplayStatus.getActiveDisplay())) {
+ final int activeState = wifiDisplayStatus.getActiveDisplayState();
+ switch (activeState) {
+ case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
+ newStatus = RouteInfo.STATUS_NONE;
+ break;
+ case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
+ newStatus = RouteInfo.STATUS_CONNECTING;
+ break;
+ case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
+ Log.e(TAG, "Active display is not connected!");
+ break;
+ }
+ }
+
+ final String newName = makeWifiDisplayName(display);
+ if (route.getName().equals(newName)) {
+ route.mName = newName;
+ changed = true;
+ }
+
+ changed |= route.mEnabled != available;
+ route.mEnabled = available;
+
+ changed |= route.setStatusCode(newStatus);
+
+ if (changed) {
+ dispatchRouteChanged(route);
+ }
+
+ if (!available && route == sStatic.mSelectedRoute) {
+ // Oops, no longer available. Reselect the default.
+ final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
+ selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
+ }
+ }
+
+ private static WifiDisplay findMatchingDisplay(WifiDisplay address, WifiDisplay[] displays) {
+ for (int i = 0; i < displays.length; i++) {
+ final WifiDisplay d = displays[i];
+ if (d.equals(address)) {
+ return d;
+ }
+ }
+ return null;
+ }
+
+ private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
+ final int count = sStatic.mRoutes.size();
+ for (int i = 0; i < count; i++) {
+ final RouteInfo info = sStatic.mRoutes.get(i);
+ if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
+ return info;
+ }
+ }
+ return null;
+ }
+
/**
* Information about a media route.
*/
@@ -644,6 +834,18 @@
int mPlaybackStream = AudioManager.STREAM_MUSIC;
VolumeCallbackInfo mVcb;
+ String mDeviceAddress;
+ boolean mEnabled = true;
+
+ // A predetermined connection status that can override mStatus
+ private int mStatusCode;
+
+ static final int STATUS_NONE = 0;
+ static final int STATUS_SCANNING = 1;
+ static final int STATUS_CONNECTING = 2;
+ static final int STATUS_AVAILABLE = 3;
+ static final int STATUS_NOT_AVAILABLE = 4;
+
private Object mTag;
/**
@@ -711,6 +913,34 @@
}
/**
+ * Set this route's status by predetermined status code. If the caller
+ * should dispatch a route changed event this call will return true;
+ */
+ boolean setStatusCode(int statusCode) {
+ if (statusCode != mStatusCode) {
+ mStatusCode = statusCode;
+ int resId = 0;
+ switch (statusCode) {
+ case STATUS_SCANNING:
+ resId = com.android.internal.R.string.media_route_status_scanning;
+ break;
+ case STATUS_CONNECTING:
+ resId = com.android.internal.R.string.media_route_status_connecting;
+ break;
+ case STATUS_AVAILABLE:
+ resId = com.android.internal.R.string.media_route_status_available;
+ break;
+ case STATUS_NOT_AVAILABLE:
+ resId = com.android.internal.R.string.media_route_status_not_available;
+ break;
+ }
+ mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
* @return A media type flag set describing which types this route supports.
*/
public int getSupportedTypes() {
@@ -866,6 +1096,13 @@
return mVolumeHandling;
}
+ /**
+ * @return true if this route is enabled and may be selected
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
void setStatusInt(CharSequence status) {
if (!status.equals(mStatus)) {
mStatus = status;
@@ -881,7 +1118,6 @@
sStatic.mHandler.post(new Runnable() {
@Override
public void run() {
- //Log.d(TAG, "dispatchRemoteVolumeUpdate dir=" + direction + " val=" + value);
if (mVcb != null) {
if (direction != 0) {
mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
@@ -1400,6 +1636,7 @@
int mNameResId;
int mTypes;
final boolean mGroupable;
+ boolean mIsSystem;
RouteCategory(CharSequence name, int types, boolean groupable) {
mName = name;
@@ -1486,6 +1723,14 @@
return mGroupable;
}
+ /**
+ * @return true if this is the category reserved for system routes.
+ * @hide
+ */
+ public boolean isSystem() {
+ return mIsSystem;
+ }
+
public String toString() {
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
" groupable=" + mGroupable + " }";
@@ -1671,7 +1916,6 @@
}
static class VolumeChangeReceiver extends BroadcastReceiver {
-
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
@@ -1689,6 +1933,15 @@
}
}
}
+ }
+ static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
+ updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
+ DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 4f0d113..e084368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1073,7 +1073,7 @@
if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
if (mTicking) {
- mTicker.halt();
+ haltTicker();
}
mNotificationIcons.animate()
@@ -1095,7 +1095,7 @@
}
} else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
- mTicker.halt();
+ haltTicker();
}
}
}
@@ -1359,7 +1359,7 @@
if (lightsOut) {
animateCollapsePanels();
if (mTicking) {
- mTicker.halt();
+ haltTicker();
}
}
@@ -1489,8 +1489,7 @@
mStatusBarContents.setVisibility(View.VISIBLE);
mTickerView.setVisibility(View.GONE);
mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
- mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
- mTickingDoneListener));
+ // we do not animate the ticker away at this point, just get rid of it (b/6992707)
}
}
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 35b9f85..165250b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -246,9 +246,10 @@
@Override
public void onClick(View v) {
mBar.collapseAllPanels(true);
- ContactsContract.QuickContact.showQuickContact(mContext, v,
- ContactsContract.Profile.CONTENT_URI,
+ Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(mContext,
+ v, ContactsContract.Profile.CONTENT_URI,
ContactsContract.QuickContact.MODE_LARGE, null);
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
}
});
mModel.addUserTile(userTile, new QuickSettingsModel.RefreshCallback() {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index e4eeceb..c3bd988 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -169,11 +169,6 @@
static final boolean SHOW_STARTING_ANIMATIONS = true;
static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
- // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
- // No longer recommended for desk docks; still useful in car docks.
- static final boolean ENABLE_CAR_DOCK_HOME_CAPTURE = true;
- static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false;
-
static final int LONG_PRESS_POWER_NOTHING = 0;
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
@@ -347,7 +342,6 @@
boolean mSystemReady;
boolean mSystemBooted;
boolean mHdmiPlugged;
- int mUiMode;
int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
int mLidOpenRotation;
int mCarDockRotation;
@@ -892,8 +886,6 @@
mSettingsObserver.observe();
mShortcutManager = new ShortcutManager(context, mHandler);
mShortcutManager.observe();
- mUiMode = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultUiModeType);
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -3563,13 +3555,6 @@
if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
Intent.EXTRA_DOCK_STATE_UNDOCKED);
- } else {
- try {
- IUiModeManager uiModeService = IUiModeManager.Stub.asInterface(
- ServiceManager.getService(Context.UI_MODE_SERVICE));
- mUiMode = uiModeService.getCurrentModeType();
- } catch (RemoteException e) {
- }
}
updateRotation(true);
updateOrientationListenerLp();
@@ -4122,63 +4107,8 @@
}
}
- /**
- * Return an Intent to launch the currently active dock app as home. Returns
- * null if the standard home should be launched, which is the case if any of the following is
- * true:
- * <ul>
- * <li>The device is not in either car mode or desk mode
- * <li>The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false
- * <li>The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false
- * <li>The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME
- * <li>The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOME
- * </ul>
- * @return
- */
- Intent createHomeDockIntent() {
- Intent intent = null;
-
- // What home does is based on the mode, not the dock state. That
- // is, when in car mode you should be taken to car home regardless
- // of whether we are actually in a car dock.
- if (mUiMode == Configuration.UI_MODE_TYPE_CAR) {
- if (ENABLE_CAR_DOCK_HOME_CAPTURE) {
- intent = mCarDockIntent;
- }
- } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK) {
- if (ENABLE_DESK_DOCK_HOME_CAPTURE) {
- intent = mDeskDockIntent;
- }
- }
-
- if (intent == null) {
- return null;
- }
-
- ActivityInfo ai = intent.resolveActivityInfo(
- mContext.getPackageManager(), PackageManager.GET_META_DATA);
- if (ai == null) {
- return null;
- }
-
- if (ai.metaData != null && ai.metaData.getBoolean(Intent.METADATA_DOCK_HOME)) {
- intent = new Intent(intent);
- intent.setClassName(ai.packageName, ai.name);
- return intent;
- }
-
- return null;
- }
-
void startDockOrHome() {
- Intent dock = createHomeDockIntent();
- if (dock != null) {
- try {
- mContext.startActivity(dock);
- return;
- } catch (ActivityNotFoundException e) {
- }
- }
+ // We don't have dock home anymore. Home is home. If you lived here, you'd be home by now.
mContext.startActivityAsUser(mHomeIntent, UserHandle.CURRENT);
}
@@ -4205,18 +4135,6 @@
} else {
ActivityManagerNative.getDefault().stopAppSwitches();
sendCloseSystemWindows();
- Intent dock = createHomeDockIntent();
- if (dock != null) {
- int result = ActivityManagerNative.getDefault()
- .startActivityAsUser(null, dock,
- dock.resolveTypeIfNeeded(mContext.getContentResolver()),
- null, null, 0,
- ActivityManager.START_FLAG_ONLY_IF_NEEDED,
- null, null, null, UserHandle.USER_CURRENT);
- if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
- return false;
- }
- }
}
int result = ActivityManagerNative.getDefault()
.startActivityAsUser(null, mHomeIntent,
@@ -4403,8 +4321,7 @@
pw.print(prefix); pw.print("mLastFocusNeedsMenu=");
pw.println(mLastFocusNeedsMenu);
}
- pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
- pw.print(" mDockMode="); pw.print(mDockMode);
+ pw.print(prefix); pw.print("mDockMode="); pw.print(mDockMode);
pw.print(" mCarDockRotation="); pw.print(mCarDockRotation);
pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation);
pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index c5ec33b..1d1c7fc 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -547,8 +547,9 @@
oldView.onPause();
newView.onResume();
+ final boolean needsInput = newView.needsInput();
if (mViewMediatorCallback != null) {
- mViewMediatorCallback.setNeedsInput(newView.needsInput());
+ mViewMediatorCallback.setNeedsInput(needsInput);
}
// Find and show this child.
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 542cc07..3e9bef0 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -102,6 +102,10 @@
// The timeout after which we are no longer trying to detect a gesture.
private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
+ // The timeout to send interaction end events in case we did not
+ // receive the expected hover exit event due to a misbehaving app.
+ private static final int SEND_INTERACTION_END_EVENTS_TIMEOUT = 200;
+
// Temporary array for storing pointer IDs.
private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
@@ -135,6 +139,9 @@
// Command for delayed sending of a hover exit event.
private final SendHoverDelayed mSendHoverExitDelayed;
+ // Command for delayed sending of interaction ending events.
+ private final SendInteractionEndEventsDelayed mSendInteractionEndEventsDelayed;
+
// Command for delayed sending of a long press.
private final PerformLongPressDelayed mPerformLongPressDelayed;
@@ -233,6 +240,7 @@
mGestureLibrary.load();
mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
+ mSendInteractionEndEventsDelayed = new SendInteractionEndEventsDelayed();
mDoubleTapDetector = new DoubleTapDetector();
final float density = context.getResources().getDisplayMetrics().density;
mScaledMinPointerDistanceToUseMiddleLocation =
@@ -278,6 +286,7 @@
mSendHoverExitDelayed.remove();
mPerformLongPressDelayed.remove();
mExitGestureDetectionModeDelayed.remove();
+ mSendInteractionEndEventsDelayed.remove();
// Reset the pointer trackers.
mReceivedPointerTracker.clear();
mInjectedPointerTracker.clear();
@@ -334,6 +343,7 @@
// last hover exit event.
if (mTouchExplorationGestureEnded
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
+ mSendInteractionEndEventsDelayed.remove();
mTouchExplorationGestureEnded = false;
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
}
@@ -342,6 +352,7 @@
// last hover exit and the touch exploration gesture end events.
if (mTouchInteractionEnded
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
+ mSendInteractionEndEventsDelayed.remove();
mTouchInteractionEnded = false;
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
}
@@ -416,6 +427,10 @@
mSendHoverExitDelayed.remove();
}
+ if (mSendInteractionEndEventsDelayed.isPending()) {
+ mSendInteractionEndEventsDelayed.forceSendAndRemove();
+ }
+
mPerformLongPressDelayed.remove();
// If we have the first tap schedule a long press and break
@@ -873,6 +888,9 @@
final int pointerIdBits = event.getPointerIdBits();
mTouchExplorationGestureEnded = true;
mTouchInteractionEnded = true;
+ if (!mSendInteractionEndEventsDelayed.isPending()) {
+ mSendInteractionEndEventsDelayed.post();
+ }
sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
}
}
@@ -1484,10 +1502,16 @@
} else {
mTouchExplorationGestureEnded = true;
mTouchInteractionEnded = true;
+ if (!mSendInteractionEndEventsDelayed.isPending()) {
+ mSendInteractionEndEventsDelayed.post();
+ }
}
} else {
if (!mGestureStarted) {
mTouchInteractionEnded = true;
+ if (!mSendInteractionEndEventsDelayed.isPending()) {
+ mSendInteractionEndEventsDelayed.post();
+ }
}
}
sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
@@ -1495,6 +1519,40 @@
}
}
+ private class SendInteractionEndEventsDelayed implements Runnable {
+
+ public void remove() {
+ mHandler.removeCallbacks(this);
+ }
+
+ public void post() {
+ mHandler.postDelayed(this, SEND_INTERACTION_END_EVENTS_TIMEOUT);
+ }
+
+ public boolean isPending() {
+ return mHandler.hasCallbacks(this);
+ }
+
+ public void forceSendAndRemove() {
+ if (isPending()) {
+ run();
+ remove();
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mTouchExplorationGestureEnded) {
+ mTouchExplorationGestureEnded = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ }
+ if (mTouchInteractionEnded) {
+ mTouchInteractionEnded = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+ }
+ }
+ }
+
@Override
public String toString() {
return LOG_TAG;
diff --git a/services/jni/com_android_server_power_PowerManagerService.cpp b/services/jni/com_android_server_power_PowerManagerService.cpp
index 38af38d..dcc2b58 100644
--- a/services/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/jni/com_android_server_power_PowerManagerService.cpp
@@ -183,14 +183,16 @@
gPowerModule->setInteractive(gPowerModule, true);
}
+ const sp<IBinder>& display = s->getBuiltInDisplay(0); // TODO: support multiple displays
{
ALOGD_IF_SLOW(100, "Excessive delay in unblank() while turning screen on");
- s->unblank();
+ s->unblank(display);
}
} else {
+ const sp<IBinder>& display = s->getBuiltInDisplay(0); // TODO: support multiple displays
{
ALOGD_IF_SLOW(100, "Excessive delay in blank() while turning screen off");
- s->blank();
+ s->blank(display);
}
if (gPowerModule) {