diff options
42 files changed, 1491 insertions, 83 deletions
diff --git a/api/current.txt b/api/current.txt index 9f1e152b46b0..a62698ef351b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3690,6 +3690,7 @@ package android.app { method public int getRouteTypes(); method public void setExtendedSettingsClickListener(android.view.View.OnClickListener); method public void setRouteTypes(int); + method public void showDialog(); } public class NativeActivity extends android.app.Activity implements android.view.InputQueue.Callback android.view.SurfaceHolder.Callback2 android.view.ViewTreeObserver.OnGlobalLayoutListener { @@ -11575,11 +11576,13 @@ package android.media { } public static class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo { + method public java.lang.Object getTag(); method public void setIconDrawable(android.graphics.drawable.Drawable); method public void setIconResource(int); method public void setName(java.lang.CharSequence); method public void setRemoteControlClient(android.media.RemoteControlClient); method public void setStatus(java.lang.CharSequence); + method public void setTag(java.lang.Object); } public class MediaScannerConnection implements android.content.ServiceConnection { diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java index 5fe08ecdb545..4860182aa4d5 100644 --- a/core/java/android/app/MediaRouteActionProvider.java +++ b/core/java/android/app/MediaRouteActionProvider.java @@ -16,7 +16,10 @@ 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; @@ -83,10 +86,37 @@ public class MediaRouteActionProvider extends ActionProvider { @Override public boolean onPerformDefaultAction() { - // Show routing dialog + 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; + } + + 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) { diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java index 385241ced8c4..a4eebda0ca0e 100644 --- a/core/java/android/app/MediaRouteButton.java +++ b/core/java/android/app/MediaRouteButton.java @@ -17,8 +17,10 @@ package android.app; import com.android.internal.R; +import com.android.internal.app.MediaRouteChooserDialogFragment; import android.content.Context; +import android.content.ContextWrapper; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -44,6 +46,7 @@ public class MediaRouteButton extends View { private int mMinHeight; private OnClickListener mExtendedSettingsClickListener; + private MediaRouteChooserDialogFragment mDialogFragment; private static final int[] ACTIVATED_STATE_SET = { R.attr.state_activated @@ -112,7 +115,7 @@ public class MediaRouteButton extends View { } } } else { - Log.d(TAG, "TODO: Implement the dialog!"); + showDialog(); } return handled; @@ -263,8 +266,51 @@ public class MediaRouteButton extends View { } public void setExtendedSettingsClickListener(OnClickListener listener) { - // TODO: if dialog is already open, propagate so that it updates live. mExtendedSettingsClickListener = listener; + if (mDialogFragment != null) { + mDialogFragment.setExtendedSettingsClickListener(listener); + } + } + + /** + * 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; + } + }); + mDialogFragment.setRouteTypes(mRouteTypes); + mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); + } + + 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 { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index ec35a3f5214a..bb497c02719e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1670,6 +1670,9 @@ public class Notification implements Parcelable contentView.setTextViewText(R.id.text, overflowText); contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); contentView.setViewVisibility(R.id.line3, View.VISIBLE); + } else { + contentView.setViewVisibility(R.id.overflow_divider, View.GONE); + contentView.setViewVisibility(R.id.line3, View.GONE); } return contentView; @@ -1812,6 +1815,7 @@ public class Notification implements Parcelable // Remove the content text so line3 only shows if you have a summary final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null); mBuilder.mContentText = null; + RemoteViews contentView = getStandardView(R.layout.notification_template_big_text); if (hadThreeLines) { diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 12f16fd3fae4..d2bed489e23d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1696,8 +1696,14 @@ public abstract class Layout { return text.getSpans(start, end, type); } + private char getEllipsisChar(TextUtils.TruncateAt method) { + return (method == TextUtils.TruncateAt.END_SMALL) ? + ELLIPSIS_TWO_DOTS[0] : + ELLIPSIS_NORMAL[0]; + } + private void ellipsize(int start, int end, int line, - char[] dest, int destoff) { + char[] dest, int destoff, TextUtils.TruncateAt method) { int ellipsisCount = getEllipsisCount(line); if (ellipsisCount == 0) { @@ -1711,7 +1717,7 @@ public abstract class Layout { char c; if (i == ellipsisStart) { - c = '\u2026'; // ellipsis + c = getEllipsisChar(method); // ellipsis } else { c = '\uFEFF'; // 0-width space } @@ -1785,7 +1791,7 @@ public abstract class Layout { TextUtils.getChars(mText, start, end, dest, destoff); for (int i = line1; i <= line2; i++) { - mLayout.ellipsize(start, end, i, dest, destoff); + mLayout.ellipsize(start, end, i, dest, destoff, mMethod); } } @@ -1890,4 +1896,6 @@ public abstract class Layout { /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT = new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG }); + /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..." + /* package */ static final char[] ELLIPSIS_TWO_DOTS = { '\u2025' }; // this is ".." } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 299e1159c27a..6973b2e39ada 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -745,7 +745,8 @@ public class StaticLayout extends Layout { } float ellipsisWidth = paint.measureText( - (where == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL); + (where == TextUtils.TruncateAt.END_SMALL) ? + ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL, 0, 1); int ellipsisStart = 0; int ellipsisCount = 0; int len = lineEnd - lineStart; @@ -985,9 +986,6 @@ public class StaticLayout extends Layout { private static final double EXTRA_ROUNDING = 0.5; - private static final String ELLIPSIS_NORMAL = "\u2026"; // this is "..." - private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // this is ".." - private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800; private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF; diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 611742b113a3..da61e7492a94 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -1316,7 +1316,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS: HitTestResult hitTest = getHitTestResult(); if (hitTest != null) { - performLongClick(); + mWebView.performLongClick(); } break; case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP: @@ -7265,7 +7265,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // the states mGotCenterDown = false; mTrackballDown = false; - performLongClick(); + mWebView.performLongClick(); break; case WEBCORE_NEED_TOUCH_EVENTS: diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java new file mode 100644 index 000000000000..bfcfdfa86a9b --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -0,0 +1,690 @@ +/* + * 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.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.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.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * This class implements the route chooser dialog for {@link MediaRouter}. + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + */ +public class MediaRouteChooserDialogFragment extends DialogFragment { + private static final String TAG = "MediaRouteChooserDialogFragment"; + public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment"; + + 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 + }; + + private static final int[] GROUP_ITEM_LAYOUTS = new int[] { + R.layout.media_route_list_item_top_header, + R.layout.media_route_list_item_checkable, + R.layout.media_route_list_item_collapse_group + }; + + MediaRouter mRouter; + private int mRouteTypes; + + private LayoutInflater mInflater; + private LauncherListener mLauncherListener; + private View.OnClickListener mExtendedSettingsListener; + private RouteAdapter mAdapter; + private GroupAdapter mGroupAdapter; + private ListView mListView; + + static final RouteComparator sComparator = new RouteComparator(); + + public MediaRouteChooserDialogFragment() { + setStyle(STYLE_NO_TITLE, 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); + } + + @Override + public void onDetach() { + super.onDetach(); + if (mLauncherListener != null) { + mLauncherListener.onDetached(this); + } + if (mGroupAdapter != null) { + mRouter.removeCallback(mGroupAdapter); + mGroupAdapter = null; + } + if (mAdapter != null) { + mRouter.removeCallback(mAdapter); + mAdapter = null; + } + mInflater = null; + mRouter = null; + } + + /** + * Implemented by the MediaRouteButton that launched this dialog + */ + public interface LauncherListener { + public void onDetached(MediaRouteChooserDialogFragment detachedFragment); + } + + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + mExtendedSettingsListener = listener; + } + + public void setRouteTypes(int types) { + mRouteTypes = types; + } + + @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); + final View extendedSettingsButton = layout.findViewById(R.id.extended_settings); + + if (mExtendedSettingsListener != null) { + extendedSettingsButton.setVisibility(View.VISIBLE); + extendedSettingsButton.setOnClickListener(mExtendedSettingsListener); + } + + final ListView list = (ListView) layout.findViewById(R.id.list); + list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + list.setItemsCanFocus(true); + list.setAdapter(mAdapter = new RouteAdapter()); + list.setItemChecked(mAdapter.getSelectedRoutePosition(), true); + list.setOnItemClickListener(mAdapter); + + mListView = list; + mRouter.addCallback(mRouteTypes, mAdapter); + + return layout; + } + + void onExpandGroup(RouteGroup info) { + mGroupAdapter = new GroupAdapter(info); + mRouter.addCallback(mRouteTypes, mGroupAdapter); + mListView.setAdapter(mGroupAdapter); + mListView.setOnItemClickListener(mGroupAdapter); + mListView.setItemsCanFocus(false); + mListView.clearChoices(); + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + mGroupAdapter.initCheckedItems(); + + getDialog().setCanceledOnTouchOutside(false); + } + + void onDoneGrouping() { + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(mAdapter); + mListView.setItemsCanFocus(true); + mListView.clearChoices(); + mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true); + + mRouter.removeCallback(mGroupAdapter); + mGroupAdapter = null; + + getDialog().setCanceledOnTouchOutside(true); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new RouteChooserDialog(getActivity(), getTheme()); + } + + @Override + public void onResume() { + super.onResume(); + + if (mListView != null) { + if (mGroupAdapter != null) { + mGroupAdapter.initCheckedItems(); + } else { + mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true); + } + } + } + + private static class ViewHolder { + public TextView text1; + public TextView text2; + public ImageView icon; + public ImageButton expandGroupButton; + public RouteAdapter.ExpandGroupListener expandGroupListener; + public int position; + } + + private class RouteAdapter extends BaseAdapter implements MediaRouter.Callback, + 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 int mSelectedItemPosition; + private final ArrayList<Object> mItems = new ArrayList<Object>(); + + RouteAdapter() { + update(); + } + + void update() { + // TODO this is kind of naive, but our data sets are going to be + // fairly small on average. + mItems.clear(); + + final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); + + final ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>(); + final int catCount = mRouter.getCategoryCount(); + for (int i = 0; i < catCount; i++) { + final RouteCategory cat = mRouter.getCategoryAt(i); + cat.getRoutes(routes); + + mItems.add(cat); + + final int routeCount = routes.size(); + for (int j = 0; j < routeCount; j++) { + final RouteInfo info = routes.get(j); + if (info == selectedRoute) { + mSelectedItemPosition = mItems.size(); + } + mItems.add(info); + } + } + + notifyDataSetChanged(); + if (mListView != null) { + mListView.setItemChecked(mSelectedItemPosition, true); + } + } + + @Override + public int getCount() { + return mItems.size(); + } + + @Override + public int getViewTypeCount() { + return 3; + } + + @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 { + return VIEW_ROUTE; + } + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItemViewType(position) == VIEW_ROUTE; + } + + @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.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; + } + + if (viewType == VIEW_ROUTE) { + bindItemView(position, holder); + } else { + bindHeaderView(position, holder); + } + + return convertView; + } + + void bindItemView(int position, ViewHolder holder) { + RouteInfo info = (RouteInfo) mItems.get(position); + holder.text1.setText(info.getName()); + 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.isGroupable()) { + final RouteGroup group = (RouteGroup) info; + canGroup = group.getRouteCount() > 1 || + getItemViewType(position - 1) == VIEW_ROUTE || + (position < getCount() - 1 && getItemViewType(position + 1) == VIEW_ROUTE); + } + 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()); + } + + public int getSelectedRoutePosition() { + return mSelectedItemPosition; + } + + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + update(); + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + update(); + } + + @Override + public void onRouteAdded(MediaRouter router, RouteInfo info) { + update(); + } + + @Override + public void onRouteRemoved(MediaRouter router, RouteInfo info) { + update(); + } + + @Override + public void onRouteChanged(MediaRouter router, RouteInfo info) { + notifyDataSetChanged(); + } + + @Override + public void onRouteGrouped(MediaRouter router, RouteInfo info, + RouteGroup group, int index) { + update(); + } + + @Override + public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { + update(); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + ListView lv = (ListView) parent; + final Object item = getItem(lv.getCheckedItemPosition()); + if (!(item instanceof RouteInfo)) { + // Oops. Stale event running around? Skip it. + return; + } + mRouter.selectRoute(mRouteTypes, (RouteInfo) item); + dismiss(); + } + + 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. + onExpandGroup((RouteGroup) getItem(position)); + } + } + } + + private class GroupAdapter extends BaseAdapter implements MediaRouter.Callback, + ListView.OnItemClickListener { + private static final int VIEW_HEADER = 0; + private static final int VIEW_ROUTE = 1; + private static final int VIEW_DONE = 2; + + private RouteGroup mPrimary; + private RouteCategory mCategory; + private final ArrayList<RouteInfo> mTempList = new ArrayList<RouteInfo>(); + private final ArrayList<RouteInfo> mFlatRoutes = new ArrayList<RouteInfo>(); + private boolean mIgnoreUpdates; + + public GroupAdapter(RouteGroup primary) { + mPrimary = primary; + mCategory = primary.getCategory(); + update(); + } + + @Override + public int getCount() { + return mFlatRoutes.size() + 2; + } + + @Override + public int getViewTypeCount() { + return 3; + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return VIEW_HEADER; + } else if (position == getCount() - 1) { + return VIEW_DONE; + } + return VIEW_ROUTE; + } + + void update() { + if (mIgnoreUpdates) return; + mFlatRoutes.clear(); + mCategory.getRoutes(mTempList); + + // Unpack groups and flatten for presentation + final int topCount = mTempList.size(); + for (int i = 0; i < topCount; i++) { + final RouteInfo route = mTempList.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); + mFlatRoutes.add(innerRoute); + } + } else { + mFlatRoutes.add(route); + } + } + mTempList.clear(); + + // Sort by name. This will keep the route positions relatively stable even though they + // will be repeatedly added and removed. + Collections.sort(mFlatRoutes, sComparator); + notifyDataSetChanged(); + } + + void initCheckedItems() { + if (mIgnoreUpdates) return; + mListView.clearChoices(); + int count = mFlatRoutes.size(); + for (int i = 0; i < count; i++){ + final RouteInfo route = mFlatRoutes.get(i); + if (route.getGroup() == mPrimary) { + mListView.setItemChecked(i + 1, true); + } + } + } + + @Override + public Object getItem(int position) { + if (position == 0) { + return mCategory; + } else if (position == getCount() - 1) { + return null; // Done + } + return mFlatRoutes.get(position - 1); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return position > 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final int viewType = getItemViewType(position); + + ViewHolder holder; + if (convertView == null) { + convertView = mInflater.inflate(GROUP_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); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + holder.position = position; + } + + if (viewType == VIEW_ROUTE) { + bindItemView(position, holder); + } else if (viewType == VIEW_HEADER) { + bindHeaderView(position, holder); + } + + return convertView; + } + + void bindItemView(int position, ViewHolder holder) { + RouteInfo info = (RouteInfo) getItem(position); + holder.text1.setText(info.getName()); + final CharSequence status = info.getStatus(); + if (TextUtils.isEmpty(status)) { + holder.text2.setVisibility(View.GONE); + } else { + holder.text2.setVisibility(View.VISIBLE); + holder.text2.setText(status); + } + } + + void bindHeaderView(int position, ViewHolder holder) { + holder.text1.setText(mCategory.getName()); + } + + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + } + + @Override + public void onRouteAdded(MediaRouter router, RouteInfo info) { + update(); + initCheckedItems(); + } + + @Override + public void onRouteRemoved(MediaRouter router, RouteInfo info) { + if (info == mPrimary) { + // Can't keep grouping, clean it up. + onDoneGrouping(); + } else { + update(); + initCheckedItems(); + } + } + + @Override + public void onRouteChanged(MediaRouter router, RouteInfo info) { + update(); + } + + @Override + public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) { + update(); + initCheckedItems(); + } + + @Override + public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { + update(); + initCheckedItems(); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (getItemViewType(position) == VIEW_DONE) { + onDoneGrouping(); + return; + } + + final ListView lv = (ListView) parent; + final RouteInfo route = mFlatRoutes.get(position - 1); + final boolean checked = lv.isItemChecked(position); + + mIgnoreUpdates = true; + RouteGroup oldGroup = route.getGroup(); + if (checked && oldGroup != mPrimary) { + // Assumption: in a groupable category oldGroup will never be null. + oldGroup.removeRoute(route); + + // If the group is now empty, remove the group too. + if (oldGroup.getRouteCount() == 0) { + 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.selectRoute(mRouteTypes, mPrimary); + } + mRouter.removeRouteInt(oldGroup); + } + + mPrimary.addRoute(route); + } else if (!checked) { + if (mPrimary.getRouteCount() > 1) { + mPrimary.removeRoute(route); + + // In a groupable category this will add the route into its own new group. + mRouter.addRouteInt(route); + } else { + // We're about to remove the last route. + // Don't let this happen, as it would be silly. + // Turn the checkmark back on again. Silly user! + lv.setItemChecked(position, true); + } + } + mIgnoreUpdates = false; + update(); + initCheckedItems(); + } + } + + static class RouteComparator implements Comparator<RouteInfo> { + @Override + public int compare(RouteInfo lhs, RouteInfo rhs) { + return lhs.getName().toString().compareTo(rhs.getName().toString()); + } + } + + class RouteChooserDialog extends Dialog { + public RouteChooserDialog(Context context, int theme) { + super(context, theme); + } + + @Override + public void onBackPressed() { + if (mGroupAdapter != null) { + onDoneGrouping(); + } else { + super.onBackPressed(); + } + } + } +} diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java new file mode 100644 index 000000000000..3fb7cec0e689 --- /dev/null +++ b/core/java/com/android/internal/view/CheckableLinearLayout.java @@ -0,0 +1,65 @@ +/* + * 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 defStyle) { + super(context, attrs, defStyle); + // 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/java/com/android/internal/view/ImageButtonNoParentPress.java b/core/java/com/android/internal/view/ImageButtonNoParentPress.java new file mode 100644 index 000000000000..a6cfd8612191 --- /dev/null +++ b/core/java/com/android/internal/view/ImageButtonNoParentPress.java @@ -0,0 +1,44 @@ +/* + * 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 android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.ImageButton; + +public class ImageButtonNoParentPress extends ImageButton { + + public ImageButtonNoParentPress(Context context) { + super(context); + } + + public ImageButtonNoParentPress(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageButtonNoParentPress(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setPressed(boolean pressed) { + // Normally parents propagate pressed state to their children. + // We don't want that to happen here; only press if our parent isn't. + super.setPressed(((ViewGroup) getParent()).isPressed() ? false : pressed); + } +} diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png Binary files differnew file mode 100644 index 000000000000..89abf2c21ac5 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_group_collapse.png diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png Binary files differnew file mode 100644 index 000000000000..d9470b2ef098 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_group_expand.png diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png Binary files differnew file mode 100644 index 000000000000..34454ac3e7e8 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_group_collapse.png diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png Binary files differnew file mode 100644 index 000000000000..8ce5a448d813 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_group_expand.png diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png Binary files differnew file mode 100644 index 000000000000..2fb7428f266f --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png Binary files differnew file mode 100644 index 000000000000..5755b9da2f03 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_group_expand.png diff --git a/core/res/res/drawable/item_background_activated_holo_dark.xml b/core/res/res/drawable/item_background_activated_holo_dark.xml new file mode 100644 index 000000000000..9cbf6abfac35 --- /dev/null +++ b/core/res/res/drawable/item_background_activated_holo_dark.xml @@ -0,0 +1,27 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. --> + <item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_disabled_holo_light" /> + <item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/list_selector_disabled_holo_light" /> + <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" /> + <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" /> + <item android:state_focused="true" android:drawable="@drawable/list_focused_holo" /> + <item android:state_activated="true" android:drawable="@android:drawable/list_activated_holo" /> + <item android:drawable="@color/transparent" /> +</selector> diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml new file mode 100644 index 000000000000..731c0d057630 --- /dev/null +++ b/core/res/res/layout/media_route_chooser_layout.xml @@ -0,0 +1,48 @@ +<?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_marginLeft="8dp" + android:layout_marginRight="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_list_item.xml b/core/res/res/layout/media_route_list_item.xml new file mode 100644 index 000000000000..ba1aaf2a2171 --- /dev/null +++ b/core/res/res/layout/media_route_list_item.xml @@ -0,0 +1,61 @@ +<?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="?android:attr/listPreferredItemHeight" + 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" /> + + <LinearLayout android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="left|center_vertical" + android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingRight="?android:attr/listPreferredItemPaddingRight"> + + <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> + + <com.android.internal.view.ImageButtonNoParentPress + 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" /> + +</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 new file mode 100644 index 000000000000..f6ba09e1e7f8 --- /dev/null +++ b/core/res/res/layout/media_route_list_item_checkable.xml @@ -0,0 +1,59 @@ +<?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: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="left|center_vertical" + android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingRight="?android:attr/listPreferredItemPaddingRight"> + + <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_marginRight="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 new file mode 100644 index 000000000000..3f4b1c0843bb --- /dev/null +++ b/core/res/res/layout/media_route_list_item_collapse_group.xml @@ -0,0 +1,39 @@ +<?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="?android:attr/listPreferredItemHeightSmall" + android:background="#19ffffff" + android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingRight="?android:attr/listPreferredItemPaddingRight" + 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> 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 new file mode 100644 index 000000000000..04bd0ea58e4b --- /dev/null +++ b/core/res/res/layout/media_route_list_item_section_header.xml @@ -0,0 +1,34 @@ +<?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:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingRight="?android:attr/listPreferredItemPaddingRight" + 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 new file mode 100644 index 000000000000..75decd38dc26 --- /dev/null +++ b/core/res/res/layout/media_route_list_item_top_header.xml @@ -0,0 +1,29 @@ +<?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:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingRight="?android:attr/listPreferredItemPaddingRight" + android:minHeight="24dp" +/> diff --git a/core/res/res/layout/notification_template_big_text.xml b/core/res/res/layout/notification_template_big_text.xml index d37788203294..0b3386b6ec8f 100644 --- a/core/res/res/layout/notification_template_big_text.xml +++ b/core/res/res/layout/notification_template_big_text.xml @@ -108,9 +108,8 @@ android:textAppearance="@style/TextAppearance.StatusBar.EventContent" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginBottom="8dp" + android:layout_marginBottom="10dp" android:layout_marginRight="8dp" - android:layout_marginTop="2dp" android:singleLine="false" android:visibility="gone" android:maxLines="8" diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e1f15cff85a2..a6f2f49c66ae 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1166,6 +1166,15 @@ <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="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_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="string" name="bluetooth_a2dp_audio_route_name" /> <!-- From android.policy --> <java-symbol type="anim" name="app_starting_exit" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 53914a8bd531..da4d37a8c109 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3551,24 +3551,29 @@ <string name="activity_resolver_use_once">Just once</string> <!-- Name of the default audio route for tablets when nothing - is connected to a headphone or other wired audio output jack. [CHAR LIMIT=25] --> + is connected to a headphone or other wired audio output jack. [CHAR LIMIT=50] --> <string name="default_audio_route_name" product="tablet">Tablet speakers</string> <!-- Name of the default audio route when nothing is connected to - a headphone or other wired audio output jack. [CHAR LIMIT=25] --> + a headphone or other wired audio output jack. [CHAR LIMIT=50] --> <string name="default_audio_route_name" product="default">Phone speaker</string> <!-- Name of the default audio route when wired headphones are - connected. [CHAR LIMIT=25] --> + connected. [CHAR LIMIT=50] --> <string name="default_audio_route_name_headphones">Headphones</string> - <!-- Name of the default audio route when an audio dock is connected. [CHAR LIMIT=25] --> + <!-- 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=25] --> + <!-- 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 audio route category. [CHAR LIMIT=25] --> + <!-- Name of the default audio route category. [CHAR LIMIT=50] --> <string name="default_audio_route_category_name">System</string> + <!-- Default name of the bluetooth a2dp audio route. [CHAR LIMIT=50] --> + <string name="bluetooth_a2dp_audio_route_name">Bluetooth audio</string> + + <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] --> + <string name="media_route_chooser_grouping_done">Done</string> </resources> diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 38fe3631886d..8488cd2058fb 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -237,6 +237,13 @@ public class MediaRouter { addRoute(info); } + /** + * @hide Framework use only + */ + public void addRouteInt(RouteInfo info) { + addRoute(info); + } + static void addRoute(RouteInfo info) { final RouteCategory cat = info.getCategory(); if (!sStatic.mCategories.contains(cat)) { @@ -246,13 +253,10 @@ public class MediaRouter { if (cat.isGroupable() && !(info instanceof RouteGroup)) { // Enforce that any added route in a groupable category must be in a group. final RouteGroup group = new RouteGroup(info.getCategory()); + group.addRoute(info); sStatic.mRoutes.add(group); dispatchRouteAdded(group); - final int at = group.getRouteCount(); - group.addRoute(info); - dispatchRouteGrouped(info, group, at); - info = group; } else { sStatic.mRoutes.add(info); @@ -282,13 +286,22 @@ public class MediaRouter { public void clearUserRoutes() { for (int i = 0; i < sStatic.mRoutes.size(); i++) { final RouteInfo info = sStatic.mRoutes.get(i); - if (info instanceof UserRouteInfo) { + // TODO Right now, RouteGroups only ever contain user routes. + // The code below will need to change if this assumption does. + if (info instanceof UserRouteInfo || info instanceof RouteGroup) { removeRouteAt(i); i--; } } } + /** + * @hide internal use only + */ + public void removeRouteInt(RouteInfo info) { + removeRoute(info); + } + static void removeRoute(RouteInfo info) { if (sStatic.mRoutes.remove(info)) { final RouteCategory removingCat = info.getCategory(); @@ -301,6 +314,11 @@ public class MediaRouter { break; } } + 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); + } if (!found) { sStatic.mCategories.remove(removingCat); } @@ -321,6 +339,11 @@ public class MediaRouter { break; } } + 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); + } if (!found) { sStatic.mCategories.remove(removingCat); } @@ -478,7 +501,8 @@ public class MediaRouter { static void onA2dpDeviceConnected() { final RouteInfo info = new RouteInfo(sStatic.mSystemCategory); - info.mName = "Bluetooth"; // TODO Fetch the real name of the device + info.mName = sStatic.mResources.getString( + com.android.internal.R.string.bluetooth_a2dp_audio_route_name); sStatic.mBluetoothA2dpRoute = info; addRoute(sStatic.mBluetoothA2dpRoute); } @@ -567,9 +591,9 @@ public class MediaRouter { @Override public String toString() { - String supportedTypes = typesToString(mSupportedTypes); - return "RouteInfo{ name=" + mName + ", status=" + mStatus + - " category=" + mCategory + + String supportedTypes = typesToString(getSupportedTypes()); + return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() + + " category=" + getCategory() + " supportedTypes=" + supportedTypes + "}"; } } @@ -581,6 +605,7 @@ public class MediaRouter { */ public static class UserRouteInfo extends RouteInfo { RemoteControlClient mRcc; + private Object mTag; UserRouteInfo(RouteCategory category) { super(category); @@ -638,6 +663,29 @@ public class MediaRouter { public void setIconResource(int resId) { setIconDrawable(sStatic.mResources.getDrawable(resId)); } + + /** + * Set an application-specific tag object for this route. + * The application may use this to store arbitrary data associated with the + * route for internal tracking. + * + * <p>Note that the lifespan of a route may be well past the lifespan of + * an Activity or other Context; take care that objects you store here + * will not keep more data in memory alive than you intend.</p> + * + * @param tag Arbitrary, app-specific data for this route to hold for later use + */ + public void setTag(Object tag) { + mTag = tag; + } + + /** + * @return The tag object previously set by the application + * @see #setTag(Object) + */ + public Object getTag() { + return mTag; + } } /** @@ -674,6 +722,7 @@ public class MediaRouter { } final int at = mRoutes.size(); mRoutes.add(route); + route.mGroup = this; mUpdateName = true; dispatchRouteGrouped(route, this, at); routeUpdated(); @@ -696,6 +745,7 @@ public class MediaRouter { " group category=" + mCategory + ")"); } mRoutes.add(insertAt, route); + route.mGroup = this; mUpdateName = true; dispatchRouteGrouped(route, this, insertAt); routeUpdated(); @@ -712,6 +762,7 @@ public class MediaRouter { " is not a member of this group."); } mRoutes.remove(route); + route.mGroup = null; mUpdateName = true; dispatchRouteUngrouped(route, this); routeUpdated(); @@ -724,6 +775,7 @@ public class MediaRouter { */ public void removeRoute(int index) { RouteInfo route = mRoutes.remove(index); + route.mGroup = null; mUpdateName = true; dispatchRouteUngrouped(route, this); routeUpdated(); @@ -775,6 +827,18 @@ public class MediaRouter { setStatusInt(status); } + @Override + void routeUpdated() { + int types = 0; + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + types |= mRoutes.get(i).mSupportedTypes; + } + mSupportedTypes = types; + mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null; + super.routeUpdated(); + } + void updateName() { final StringBuilder sb = new StringBuilder(); final int count = mRoutes.size(); @@ -786,6 +850,19 @@ public class MediaRouter { mName = sb.toString(); mUpdateName = false; } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(super.toString()); + sb.append('['); + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + if (i > 0) sb.append(", "); + sb.append(mRoutes.get(i)); + } + sb.append(']'); + return sb.toString(); + } } /** @@ -860,7 +937,7 @@ public class MediaRouter { public String toString() { return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) + - " groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }"; + " groupable=" + mGroupable + " }"; } } diff --git a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java index 52c9fda0333d..78f7f3ee1d94 100644 --- a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java +++ b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java @@ -29,6 +29,7 @@ import android.filterfw.core.ShaderProgram; import android.filterfw.format.ImageFormat; import android.opengl.GLES20; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import java.lang.ArrayIndexOutOfBoundsException; @@ -510,6 +511,20 @@ public class BackDropperFilter extends Filter { super(name); mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + String adjStr = SystemProperties.get("ro.media.effect.bgdropper.adj"); + if (adjStr.length() > 0) { + try { + mAcceptStddev += Float.parseFloat(adjStr); + if (mLogVerbose) { + Log.v(TAG, "Adjusting accept threshold by " + adjStr + + ", now " + mAcceptStddev); + } + } catch (NumberFormatException e) { + Log.e(TAG, + "Badly formatted property ro.media.effect.bgdropper.adj: " + adjStr); + } + } } @Override @@ -695,7 +710,6 @@ public class BackDropperFilter extends Filter { mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); mFrameCount = 0; - mStartLearning = false; } // Select correct pingpong buffers @@ -720,6 +734,11 @@ public class BackDropperFilter extends Filter { mBgInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_NEAREST); + if (mStartLearning) { + copyShaderProgram.process(mVideoInput, mBgMean[inputIndex]); + mStartLearning = false; + } + // Process shaders Frame[] distInputs = { mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex] }; mBgDistProgram.process(distInputs, mDistance); @@ -765,7 +784,12 @@ public class BackDropperFilter extends Filter { ByteBuffer mMaskAverageByteBuffer = mMaskAverage.getData(); byte[] mask_average = mMaskAverageByteBuffer.array(); int bi = (int)(mask_average[3] & 0xFF); - if (mLogVerbose) Log.v(TAG, String.format("Mask_average is %d", bi)); + + if (mLogVerbose) { + Log.v(TAG, + String.format("Mask_average is %d, threshold is %d", + bi, DEFAULT_LEARNING_DONE_THRESHOLD)); + } if (bi >= DEFAULT_LEARNING_DONE_THRESHOLD) { mStartLearning = true; // Restart learning diff --git a/packages/SystemUI/res/drawable/system_bar_notification_header_bg.xml b/packages/SystemUI/res/drawable/system_bar_notification_header_bg.xml new file mode 100644 index 000000000000..85f1ea215849 --- /dev/null +++ b/packages/SystemUI/res/drawable/system_bar_notification_header_bg.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@*android:drawable/list_selector_pressed_holo_dark" /> + <item android:drawable="@*android:drawable/list_selector_disabled_holo_dark" /> +</selector> diff --git a/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml b/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml index afe3b49aef1a..480d979cd488 100644 --- a/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml +++ b/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml @@ -18,7 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:id="@+id/title_area" - android:background="@color/notification_panel_solid_background" + android:background="@drawable/system_bar_notification_header_bg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index df41d255fa89..48efbc7217e6 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -23,11 +23,14 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.util.Log; +import android.view.Gravity; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; +import android.widget.ScrollView; +import android.widget.FrameLayout; public class ExpandHelper implements Gefingerpoken, OnClickListener { public interface Callback { @@ -38,7 +41,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { } private static final String TAG = "ExpandHelper"; - protected static final boolean DEBUG = false; + protected static final boolean DEBUG = true; private static final long EXPAND_DURATION = 250; private static final long GLOW_DURATION = 150; @@ -82,8 +85,11 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { private int mLargeSize; private float mMaximumStretch; + private int mGravity; + private class ViewScaler { View mView; + public ViewScaler() {} public void setView(View v) { mView = v; @@ -119,6 +125,15 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { } } + /** + * Handle expansion gestures to expand and contract children of the callback. + * + * @param context application context + * @param callback the container that holds the items to be manipulated + * @param small the smallest allowable size for the manuipulated items. + * @param large the largest allowable size for the manuipulated items. + * @param scoller if non-null also manipulate the scroll position to obey the gravity. + */ public ExpandHelper(Context context, Callback callback, int small, int large) { mSmallSize = small; mMaximumStretch = mSmallSize * STRETCH_INTERVAL; @@ -126,7 +141,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mContext = context; mCallback = callback; mScaler = new ViewScaler(); - + mGravity = Gravity.TOP; mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f); mScaleAnimation.setDuration(EXPAND_DURATION); @@ -194,6 +209,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { span *= USE_SPAN ? 1f : 0f; float drag = detector.getFocusY() - mInitialTouchFocusY; drag *= USE_DRAG ? 1f : 0f; + drag *= mGravity == Gravity.BOTTOM ? -1f : 1f; float pull = Math.abs(drag) + Math.abs(span) + 1f; float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull; if (DEBUG) Log.d(TAG, "current span handle is: " + hand); @@ -227,6 +243,10 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mEventSource = eventSource; } + public void setGravity(int gravity) { + mGravity = gravity; + } + public void setGlow(float glow) { if (!mGlowAnimationSet.isRunning() || glow == 0f) { if (mGlowAnimationSet.isRunning()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 9c773a58623d..00d6d6f28c18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -225,11 +225,28 @@ public class NavigationBarView extends LinearLayout { final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0); + setSlippery(disableHome && disableRecent && disableBack); + getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); } + public void setSlippery(boolean newSlippery) { + WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); + if (lp != null) { + boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0; + if (!oldSlippery && newSlippery) { + lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; + } else if (oldSlippery && !newSlippery) { + lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; + } else { + return; + } + WindowManagerImpl.getDefault().updateViewLayout(this, lp); + } + } + public void setMenuVisibility(final boolean show) { setMenuVisibility(show, false); } 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 56de506c3380..baf86f397646 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -171,6 +171,7 @@ public class PhoneStatusBar extends BaseStatusBar { final Rect mNotificationPanelBackgroundPadding = new Rect(); int mNotificationPanelGravity; int mNotificationPanelMinHeight; + boolean mNotificationPanelIsFullScreenWidth; // top bar View mClearButton; @@ -361,9 +362,11 @@ public class PhoneStatusBar extends BaseStatusBar { return true; } }); + mNotificationPanelIsFullScreenWidth = + (mNotificationPanel.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT); mNotificationPanel.setSystemUiVisibility( View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER - | View.STATUS_BAR_DISABLE_SYSTEM_INFO); + | (mNotificationPanelIsFullScreenWidth ? 0 : View.STATUS_BAR_DISABLE_SYSTEM_INFO)); if (!ActivityManager.isHighEndGfx(mDisplay)) { mStatusBarWindow.setBackground(null); @@ -376,6 +379,8 @@ public class PhoneStatusBar extends BaseStatusBar { mIntruderAlertView.setBar(this); } + updateShowSearchHoldoff(); + mStatusBarView.mService = this; mChoreographer = Choreographer.getInstance(); @@ -663,7 +668,6 @@ public class PhoneStatusBar extends BaseStatusBar { lp.setTitle("NavigationBar"); lp.windowAnimations = 0; - return lp; } @@ -808,8 +812,12 @@ public class PhoneStatusBar extends BaseStatusBar { @Override protected void onConfigurationChanged(Configuration newConfig) { updateRecentsPanel(); - mShowSearchHoldoff = mContext.getResources().getInteger( - R.integer.config_show_search_delay); + updateShowSearchHoldoff(); + } + + private void updateShowSearchHoldoff() { + mShowSearchHoldoff = mContext.getResources().getInteger( + R.integer.config_show_search_delay); } private void loadNotificationShade() { @@ -1174,7 +1182,8 @@ public class PhoneStatusBar extends BaseStatusBar { mExpandedVisible = true; mPile.setLayoutTransitionsEnabled(true); - makeSlippery(mNavigationBarView, true); + if (mNavigationBarView != null) + mNavigationBarView.setSlippery(true); updateCarrierLabelVisibility(true); @@ -1198,19 +1207,6 @@ public class PhoneStatusBar extends BaseStatusBar { visibilityChanged(true); } - private static void makeSlippery(View view, boolean slippery) { - if (view == null) { - return; - } - WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams(); - if (slippery) { - lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; - } else { - lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; - } - WindowManagerImpl.getDefault().updateViewLayout(view, lp); - } - public void animateExpand() { if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { @@ -1295,8 +1291,9 @@ public class PhoneStatusBar extends BaseStatusBar { } mExpandedVisible = false; mPile.setLayoutTransitionsEnabled(false); + if (mNavigationBarView != null) + mNavigationBarView.setSlippery(false); visibilityChanged(false); - makeSlippery(mNavigationBarView, false); // Shrink the window to the size of the status bar only WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index ed1b2f5c7faf..931756197c23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -22,6 +22,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.widget.FrameLayout; +import android.widget.ScrollView; import android.widget.TextSwitcher; import com.android.systemui.ExpandHelper; @@ -47,6 +48,7 @@ public class StatusBarWindowView extends FrameLayout protected void onAttachedToWindow () { super.onAttachedToWindow(); latestItems = (NotificationRowLayout) findViewById(R.id.latestItems); + ScrollView scroller = (ScrollView) findViewById(R.id.scroll); int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_min_height); int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_max_height); mExpandHelper = new ExpandHelper(mContext, latestItems, minHeight, maxHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java index d8166f1bae01..af0f9d3496dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java @@ -24,6 +24,7 @@ import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Slog; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -33,6 +34,7 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.RelativeLayout; +import android.widget.ScrollView; import com.android.systemui.ExpandHelper; import com.android.systemui.R; @@ -114,6 +116,7 @@ public class NotificationPanel extends RelativeLayout implements StatusBarPanel, int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_max_height); mExpandHelper = new ExpandHelper(mContext, latestItems, minHeight, maxHeight); mExpandHelper.setEventSource(this); + mExpandHelper.setGravity(Gravity.BOTTOM); } private View.OnClickListener mClearButtonListener = new View.OnClickListener() { diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java index 18b804217904..87ec16b23419 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java +++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java @@ -81,7 +81,7 @@ public class KeyguardUpdateMonitor { private int mFailedAttempts = 0; private int mFailedBiometricUnlockAttempts = 0; - private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 5; + private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 3; private boolean mClockVisible; diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java index d7fb19a3f785..58d14eddb79a 100644 --- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java +++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java @@ -58,7 +58,6 @@ public class LockPatternKeyguardViewProperties implements KeyguardViewProperties final IccCard.State simState = mUpdateMonitor.getSimState(); return (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED - || simState == IccCard.State.ABSENT || simState == IccCard.State.PERM_DISABLED); } diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 985249d8d6bc..2918dbcdb635 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -1702,7 +1702,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mProximityAlerts.remove(intent); if (mProximityAlerts.size() == 0) { - removeUpdatesLocked(mProximityReceiver); + if (mProximityReceiver != null) { + removeUpdatesLocked(mProximityReceiver); + } mProximityReceiver = null; mProximityListener = null; } diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index a36c67305d35..152e1889e60d 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -98,6 +98,9 @@ public class TouchExplorer { // the two dragging pointers as opposed to use the location of the primary one. private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200; + // The timeout after which we are no longer trying to detect a gesture. + private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000; + // Temporary array for storing pointer IDs. private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; @@ -138,6 +141,9 @@ public class TouchExplorer { // Command for delayed sending of a long press. private final PerformLongPressDelayed mPerformLongPressDelayed; + // Command for exiting gesture detection mode after a timeout. + private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed; + // Helper to detect and react to double tap in touch explore mode. private final DoubleTapDetector mDoubleTapDetector; @@ -212,6 +218,7 @@ public class TouchExplorer { mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); mHandler = new Handler(context.getMainLooper()); mPerformLongPressDelayed = new PerformLongPressDelayed(); + mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures); mGestureLibrary.setOrientationStyle(4); mGestureLibrary.load(); @@ -257,6 +264,7 @@ public class TouchExplorer { mSendHoverEnterDelayed.remove(); mSendHoverExitDelayed.remove(); mPerformLongPressDelayed.remove(); + mExitGestureDetectionModeDelayed.remove(); // Reset the pointer trackers. mReceivedPointerTracker.clear(); mInjectedPointerTracker.clear(); @@ -304,9 +312,9 @@ public class TouchExplorer { switch (eventType) { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { - if (mInjectedPointerTracker.mLastInjectedHoverEvent != null) { - mInjectedPointerTracker.mLastInjectedHoverEvent.recycle(); - mInjectedPointerTracker.mLastInjectedHoverEvent = null; + if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) { + mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle(); + mInjectedPointerTracker.mLastInjectedHoverEventForClick = null; } mLastTouchedWindowId = -1; } break; @@ -420,6 +428,7 @@ public class TouchExplorer { mSendHoverEnterDelayed.remove(); mSendHoverExitDelayed.remove(); mPerformLongPressDelayed.remove(); + mExitGestureDetectionModeDelayed.post(); } else { // We have just decided that the user is touch, // exploring so start sending events. @@ -727,6 +736,7 @@ public class TouchExplorer { } mStrokeBuffer.clear(); + mExitGestureDetectionModeDelayed.remove(); mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_CANCEL: { @@ -1067,7 +1077,8 @@ public class TouchExplorer { final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex()); final int pointerIndex = secondTapUp.findPointerIndex(pointerId); - MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEvent(); + MotionEvent lastExploreEvent = + mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in // the active window. We click in the middle of the focus bounds. @@ -1263,6 +1274,25 @@ public class TouchExplorer { } /** + * Class for delayed exiting from gesture detecting mode. + */ + private final class ExitGestureDetectionModeDelayed implements Runnable { + + public void post() { + mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT); + } + + public void remove() { + mHandler.removeCallbacks(this); + } + + @Override + public void run() { + clear(); + } + } + + /** * Class for delayed sending of long press. */ private final class PerformLongPressDelayed implements Runnable { @@ -1299,7 +1329,8 @@ public class TouchExplorer { final int pointerId = mEvent.getPointerId(mEvent.getActionIndex()); final int pointerIndex = mEvent.findPointerIndex(pointerId); - MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEvent(); + MotionEvent lastExploreEvent = + mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in // the active window. We click in the middle of the focus bounds. @@ -1453,6 +1484,9 @@ public class TouchExplorer { // The last injected hover event. private MotionEvent mLastInjectedHoverEvent; + // The last injected hover event used for performing clicks. + private MotionEvent mLastInjectedHoverEventForClick; + /** * Processes an injected {@link MotionEvent} event. * @@ -1484,6 +1518,10 @@ public class TouchExplorer { mLastInjectedHoverEvent.recycle(); } mLastInjectedHoverEvent = MotionEvent.obtain(event); + if (mLastInjectedHoverEventForClick != null) { + mLastInjectedHoverEventForClick.recycle(); + } + mLastInjectedHoverEventForClick = MotionEvent.obtain(event); } break; } if (DEBUG) { @@ -1537,6 +1575,13 @@ public class TouchExplorer { return mLastInjectedHoverEvent; } + /** + * @return The the last injected hover event. + */ + public MotionEvent getLastInjectedHoverEventForClick() { + return mLastInjectedHoverEventForClick; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index efed0a4a3721..267bf828d326 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -93,6 +93,7 @@ public class WindowAnimator { final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; if (!winAnimator.mLastHidden) { winAnimator.hide(); + mService.dispatchWallpaperVisibility(wallpaper, false); mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } } @@ -292,7 +293,7 @@ public class WindowAnimator { + " anim=" + win.mWinAnimator.mAnimation); } else if (mPolicy.canBeForceHidden(win, win.mAttrs)) { final boolean changed; - if (mForceHiding) { + if (mForceHiding && !winAnimator.isAnimating()) { changed = win.hideLw(false, false); if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, "Now policy hidden: " + win); diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index b85573f82849..6d5ae71427bd 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -1870,16 +1870,7 @@ public class WindowManagerService extends IWindowManager.Stub // First, make sure the client has the current visibility // state. - if (wallpaper.mWallpaperVisible != visible) { - wallpaper.mWallpaperVisible = visible; - try { - if (DEBUG_VISIBILITY || DEBUG_WALLPAPER) Slog.v(TAG, - "Setting visibility of wallpaper " + wallpaper - + ": " + visible); - wallpaper.mClient.dispatchAppVisibility(visible); - } catch (RemoteException e) { - } - } + dispatchWallpaperVisibility(wallpaper, visible); wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment; if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "adjustWallpaper win " @@ -2089,6 +2080,24 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Check wallpaper for visiblity change and notify window if so. + * @param wallpaper The wallpaper to test and notify. + * @param visible Current visibility. + */ + void dispatchWallpaperVisibility(final WindowState wallpaper, final boolean visible) { + if (wallpaper.mWallpaperVisible != visible) { + wallpaper.mWallpaperVisible = visible; + try { + if (DEBUG_VISIBILITY || DEBUG_WALLPAPER) Slog.v(TAG, + "Updating visibility of wallpaper " + wallpaper + + ": " + visible + " Callers=" + Debug.getCallers(2)); + wallpaper.mClient.dispatchAppVisibility(visible); + } catch (RemoteException e) { + } + } + } + void updateWallpaperVisibilityLocked() { final boolean visible = isWallpaperVisible(mWallpaperTarget); final int dw = mAppDisplayWidth; @@ -2113,16 +2122,7 @@ public class WindowManagerService extends IWindowManager.Stub updateWallpaperOffsetLocked(wallpaper, dw, dh, false); } - if (wallpaper.mWallpaperVisible != visible) { - wallpaper.mWallpaperVisible = visible; - try { - if (DEBUG_VISIBILITY || DEBUG_WALLPAPER) Slog.v(TAG, - "Updating visibility of wallpaper " + wallpaper - + ": " + visible); - wallpaper.mClient.dispatchAppVisibility(visible); - } catch (RemoteException e) { - } - } + dispatchWallpaperVisibility(wallpaper, visible); } } } diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index bdacb6e23dac..579cbb7b5119 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -1122,6 +1122,9 @@ class WindowStateAnimator { + " during relayout"); if (showSurfaceRobustlyLocked()) { mLastHidden = false; + if (w.mIsWallpaper) { + mService.dispatchWallpaperVisibility(w, true); + } } else { w.mOrientationChanging = false; } |