diff options
12 files changed, 489 insertions, 80 deletions
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 97b85e2..fdd34f5 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -471,7 +471,7 @@ public final class Debug * </tbody> * </table> */ - public String getMemoryStat(String statName) { + public String getMemoryStat(String statName) { switch(statName) { case "summary.java-heap": return Integer.toString(getSummaryJavaHeap()); @@ -1538,7 +1538,13 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo /** * Retrieves information about this processes memory usages. This information is broken down by - * how much is in use by dalivk, the native heap, and everything else. + * how much is in use by dalvik, the native heap, and everything else. + * + * <p><b>Note:</b> this method directly retrieves memory information for the give process + * from low-level data available to it. It may not be able to retrieve information about + * some protected allocations, such as graphics. If you want to be sure you can see + * all information about allocations by the process, use instead + * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])}.</p> */ public static native void getMemoryInfo(MemoryInfo memoryInfo); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 2e0bf06..6bbebb7 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -16,6 +16,8 @@ package com.android.internal.app; +import android.animation.ObjectAnimator; +import android.annotation.NonNull; import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -29,6 +31,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.database.DataSetObserver; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -46,13 +49,18 @@ import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; import android.service.chooser.IChooserTargetService; import android.text.TextUtils; +import android.util.FloatProperty; import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; +import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.ListView; @@ -80,6 +88,7 @@ public class ChooserActivity extends ResolverActivity { private Intent mReferrerFillInIntent; private ChooserListAdapter mChooserListAdapter; + private ChooserRowAdapter mChooserRowAdapter; private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); @@ -253,7 +262,9 @@ public class ChooserActivity extends ResolverActivity { boolean alwaysUseOption) { final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; mChooserListAdapter = (ChooserListAdapter) adapter; - adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter)); + mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter); + mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView)); + adapterView.setAdapter(mChooserRowAdapter); if (listView != null) { listView.setItemsCanFocus(true); } @@ -913,19 +924,103 @@ public class ChooserActivity extends ResolverActivity { } } + static class RowScale { + private static final int DURATION = 400; + + float mScale; + ChooserRowAdapter mAdapter; + private final ObjectAnimator mAnimator; + + public static final FloatProperty<RowScale> PROPERTY = + new FloatProperty<RowScale>("scale") { + @Override + public void setValue(RowScale object, float value) { + object.mScale = value; + object.mAdapter.notifyDataSetChanged(); + } + + @Override + public Float get(RowScale object) { + return object.mScale; + } + }; + + public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) { + mAdapter = adapter; + mScale = from; + if (from == to) { + mAnimator = null; + return; + } + + mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION); + } + + public RowScale setInterpolator(Interpolator interpolator) { + if (mAnimator != null) { + mAnimator.setInterpolator(interpolator); + } + return this; + } + + public float get() { + return mScale; + } + + public void startAnimation() { + if (mAnimator != null) { + mAnimator.start(); + } + } + + public void cancelAnimation() { + if (mAnimator != null) { + mAnimator.cancel(); + } + } + } + class ChooserRowAdapter extends BaseAdapter { private ChooserListAdapter mChooserListAdapter; private final LayoutInflater mLayoutInflater; private final int mColumnCount = 4; + private RowScale[] mServiceTargetScale; + private final Interpolator mInterpolator; public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) { mChooserListAdapter = wrappedAdapter; mLayoutInflater = LayoutInflater.from(ChooserActivity.this); + mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this, + android.R.interpolator.decelerate_quint); + wrappedAdapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { super.onChanged(); + final int rcount = getServiceTargetRowCount(); + if (mServiceTargetScale == null + || mServiceTargetScale.length != rcount) { + RowScale[] old = mServiceTargetScale; + int oldRCount = old != null ? old.length : 0; + mServiceTargetScale = new RowScale[rcount]; + if (old != null && rcount > 0) { + System.arraycopy(old, 0, mServiceTargetScale, 0, + Math.min(old.length, rcount)); + } + + for (int i = rcount; i < oldRCount; i++) { + old[i].cancelAnimation(); + } + + for (int i = oldRCount; i < rcount; i++) { + final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f) + .setInterpolator(mInterpolator); + mServiceTargetScale[i] = rs; + rs.startAnimation(); + } + } + notifyDataSetChanged(); } @@ -933,19 +1028,43 @@ public class ChooserActivity extends ResolverActivity { public void onInvalidated() { super.onInvalidated(); notifyDataSetInvalidated(); + if (mServiceTargetScale != null) { + for (RowScale rs : mServiceTargetScale) { + rs.cancelAnimation(); + } + } } }); } + private float getRowScale(int rowPosition) { + final int start = getCallerTargetRowCount(); + final int end = start + getServiceTargetRowCount(); + if (rowPosition >= start && rowPosition < end) { + return mServiceTargetScale[rowPosition - start].get(); + } + return 1.f; + } + @Override public int getCount() { return (int) ( - Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount) - + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount) + getCallerTargetRowCount() + + getServiceTargetRowCount() + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) ); } + public int getCallerTargetRowCount() { + return (int) Math.ceil( + (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount); + } + + public int getServiceTargetRowCount() { + return (int) Math.ceil( + (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount); + } + @Override public Object getItem(int position) { // We have nothing useful to return here. @@ -959,33 +1078,67 @@ public class ChooserActivity extends ResolverActivity { @Override public View getView(int position, View convertView, ViewGroup parent) { - final View[] holder; + final RowViewHolder holder; if (convertView == null) { holder = createViewHolder(parent); } else { - holder = (View[]) convertView.getTag(); + holder = (RowViewHolder) convertView.getTag(); } bindViewHolder(position, holder); - // We keep the actual list item view as the last item in the holder array - return holder[mColumnCount]; + return holder.row; } - View[] createViewHolder(ViewGroup parent) { - final View[] holder = new View[mColumnCount + 1]; - + RowViewHolder createViewHolder(ViewGroup parent) { final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent, false); + final RowViewHolder holder = new RowViewHolder(row, mColumnCount); + final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + for (int i = 0; i < mColumnCount; i++) { - holder[i] = mChooserListAdapter.createView(row); - row.addView(holder[i]); + final View v = mChooserListAdapter.createView(row); + v.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startSelected(holder.itemIndex, false, true); + } + }); + v.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showAppDetails( + mChooserListAdapter.resolveInfoForPosition(holder.itemIndex, true)); + return true; + } + }); + row.addView(v); + holder.cells[i] = v; + + // Force height to be a given so we don't have visual disruption during scaling. + LayoutParams lp = v.getLayoutParams(); + v.measure(spec, spec); + if (lp == null) { + lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight()); + row.setLayoutParams(lp); + } else { + lp.height = v.getMeasuredHeight(); + } + } + + // Pre-measure so we can scale later. + holder.measure(); + LayoutParams lp = row.getLayoutParams(); + if (lp == null) { + lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight); + row.setLayoutParams(lp); + } else { + lp.height = holder.measuredRowHeight; } row.setTag(holder); - holder[mColumnCount] = row; return holder; } - void bindViewHolder(int rowPosition, View[] holder) { + void bindViewHolder(int rowPosition, RowViewHolder holder) { final int start = getFirstRowPosition(rowPosition); final int startType = mChooserListAdapter.getPositionTargetType(start); @@ -994,34 +1147,26 @@ public class ChooserActivity extends ResolverActivity { end--; } - final ViewGroup row = (ViewGroup) holder[mColumnCount]; - if (startType == ChooserListAdapter.TARGET_SERVICE) { - row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color)); + holder.row.setBackgroundColor( + getColor(R.color.chooser_service_row_background_color)); } else { - row.setBackground(null); + holder.row.setBackgroundColor(Color.TRANSPARENT); + } + + final int oldHeight = holder.row.getLayoutParams().height; + holder.row.getLayoutParams().height = Math.max(1, + (int) (holder.measuredRowHeight * getRowScale(rowPosition))); + if (holder.row.getLayoutParams().height != oldHeight) { + holder.row.requestLayout(); } for (int i = 0; i < mColumnCount; i++) { - final View v = holder[i]; + final View v = holder.cells[i]; if (start + i <= end) { v.setVisibility(View.VISIBLE); - final int itemIndex = start + i; - mChooserListAdapter.bindView(itemIndex, v); - v.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startSelected(itemIndex, false, true); - } - }); - v.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - showAppDetails( - mChooserListAdapter.resolveInfoForPosition(itemIndex, true)); - return true; - } - }); + holder.itemIndex = start + i; + mChooserListAdapter.bindView(holder.itemIndex, v); } else { v.setVisibility(View.GONE); } @@ -1048,6 +1193,24 @@ public class ChooserActivity extends ResolverActivity { } } + static class RowViewHolder { + final View[] cells; + final ViewGroup row; + int measuredRowHeight; + int itemIndex; + + public RowViewHolder(ViewGroup row, int cellCount) { + this.row = row; + this.cells = new View[cellCount]; + } + + public void measure() { + final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + row.measure(spec, spec); + measuredRowHeight = row.getMeasuredHeight(); + } + } + static class ChooserTargetServiceConnection implements ServiceConnection { private final DisplayResolveInfo mOriginalTarget; private ComponentName mConnectedComponent; @@ -1199,4 +1362,44 @@ public class ChooserActivity extends ResolverActivity { mSelectedTarget = null; } } + + class OffsetDataSetObserver extends DataSetObserver { + private final AbsListView mListView; + private int mCachedViewType = -1; + private View mCachedView; + + public OffsetDataSetObserver(AbsListView listView) { + mListView = listView; + } + + @Override + public void onChanged() { + if (mResolverDrawerLayout == null) { + return; + } + + final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount(); + int offset = 0; + for (int i = 0; i < chooserTargetRows; i++) { + final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i; + final int vt = mChooserRowAdapter.getItemViewType(pos); + if (vt != mCachedViewType) { + mCachedView = null; + } + final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView); + int height = ((RowViewHolder) (v.getTag())).measuredRowHeight; + + offset += (int) (height * mChooserRowAdapter.getRowScale(pos) * chooserTargetRows); + + if (vt >= 0) { + mCachedViewType = vt; + mCachedView = v; + } else { + mCachedViewType = -1; + } + } + + mResolverDrawerLayout.setCollapsibleHeightReserved(offset); + } + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 1710489..ba0912a 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -103,6 +103,8 @@ public class ResolverActivity extends Activity { private ResolverComparator mResolverComparator; private PickTargetOptionRequest mPickOptionRequest; + protected ResolverDrawerLayout mResolverDrawerLayout; + private boolean mRegistered; private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { @@ -253,6 +255,7 @@ public class ResolverActivity extends Activity { if (isVoiceInteraction()) { rdl.setCollapsed(false); } + mResolverDrawerLayout = rdl; } if (title == null) { @@ -1570,7 +1573,10 @@ public class ResolverActivity extends Activity { private void onBindView(View view, TargetInfo info) { final ViewHolder holder = (ViewHolder) view.getTag(); - holder.text.setText(info.getDisplayLabel()); + final CharSequence label = info.getDisplayLabel(); + if (!TextUtils.equals(holder.text.getText(), label)) { + holder.text.setText(info.getDisplayLabel()); + } if (showsExtendedInfo(info)) { holder.text2.setVisibility(View.VISIBLE); holder.text2.setText(info.getExtendedInfo()); diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index 7679624..c4347f8 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -69,6 +69,12 @@ public class ResolverDrawerLayout extends ViewGroup { private int mCollapsibleHeight; private int mUncollapsibleHeight; + /** + * The height in pixels of reserved space added to the top of the collapsed UI; + * e.g. chooser targets + */ + private int mCollapsibleHeightReserved; + private int mTopOffset; private boolean mIsDragging; @@ -153,12 +159,62 @@ public class ResolverDrawerLayout extends ViewGroup { } } + public void setCollapsibleHeightReserved(int heightPixels) { + final int oldReserved = mCollapsibleHeightReserved; + mCollapsibleHeightReserved = heightPixels; + + final int dReserved = mCollapsibleHeightReserved - oldReserved; + if (dReserved != 0 && mIsDragging) { + mLastTouchY -= dReserved; + } + + final int oldCollapsibleHeight = mCollapsibleHeight; + mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight()); + + if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) { + return; + } + + invalidate(); + } + private boolean isMoving() { return mIsDragging || !mScroller.isFinished(); } + private boolean isDragging() { + return mIsDragging || getNestedScrollAxes() == SCROLL_AXIS_VERTICAL; + } + + private boolean updateCollapseOffset(int oldCollapsibleHeight, boolean remainClosed) { + if (oldCollapsibleHeight == mCollapsibleHeight) { + return false; + } + + if (isLaidOut()) { + final boolean isCollapsedOld = mCollapseOffset != 0; + if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight + && mCollapseOffset == oldCollapsibleHeight)) { + // Stay closed even at the new height. + mCollapseOffset = mCollapsibleHeight; + } else { + mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight); + } + final boolean isCollapsedNew = mCollapseOffset != 0; + if (isCollapsedOld != isCollapsedNew) { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + } else { + // Start out collapsed at first unless we restored state for otherwise + mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight; + } + return true; + } + private int getMaxCollapsedHeight() { - return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight; + return (isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight) + + mCollapsibleHeightReserved; } public void setOnDismissedListener(OnDismissedListener listener) { @@ -676,7 +732,7 @@ public class ResolverDrawerLayout extends ViewGroup { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.alwaysShow && child.getVisibility() != GONE) { measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed); - heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin; + heightUsed += getHeightUsed(child); } } @@ -688,7 +744,7 @@ public class ResolverDrawerLayout extends ViewGroup { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.alwaysShow && child.getVisibility() != GONE) { measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed); - heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin; + heightUsed += getHeightUsed(child); } } @@ -697,30 +753,43 @@ public class ResolverDrawerLayout extends ViewGroup { heightUsed - alwaysShowHeight - getMaxCollapsedHeight()); mUncollapsibleHeight = heightUsed - mCollapsibleHeight; - if (isLaidOut()) { - final boolean isCollapsedOld = mCollapseOffset != 0; - if (oldCollapsibleHeight < mCollapsibleHeight - && mCollapseOffset == oldCollapsibleHeight) { - // Stay closed even at the new height. - mCollapseOffset = mCollapsibleHeight; - } else { - mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight); - } - final boolean isCollapsedNew = mCollapseOffset != 0; - if (isCollapsedOld != isCollapsedNew) { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); - } - } else { - // Start out collapsed at first unless we restored state for otherwise - mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight; - } + updateCollapseOffset(oldCollapsibleHeight, !isDragging()); mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset; setMeasuredDimension(sourceWidth, heightSize); } + private int getHeightUsed(View child) { + // This method exists because we're taking a fast path at measuring ListViews that + // lets us get away with not doing the more expensive wrap_content measurement which + // imposes double child view measurement costs. If we're looking at a ListView, we can + // check against the lowest child view plus padding and margin instead of the actual + // measured height of the ListView. This lets the ListView hang off the edge when + // all of the content would fit on-screen. + + int heightUsed = child.getMeasuredHeight(); + if (child instanceof AbsListView) { + final AbsListView lv = (AbsListView) child; + final int lvPaddingBottom = lv.getPaddingBottom(); + + int lowest = 0; + for (int i = 0, N = lv.getChildCount(); i < N; i++) { + final int bottom = lv.getChildAt(i).getBottom() + lvPaddingBottom; + if (bottom > lowest) { + lowest = bottom; + } + } + + if (lowest < heightUsed) { + heightUsed = lowest; + } + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + return lp.topMargin + heightUsed + lp.bottomMargin; + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int width = getWidth(); diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index dcdfb6c..41726fb 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -85,7 +85,7 @@ <ListView android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:id="@+id/resolver_list" android:clipToPadding="false" android:scrollbarStyle="outsideOverlay" diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml index 1c496f6..0a7ac77 100644 --- a/core/res/res/layout/resolve_grid_item.xml +++ b/core/res/res/layout/resolve_grid_item.xml @@ -70,6 +70,7 @@ android:minLines="2" android:maxLines="2" android:gravity="top|center_horizontal" - android:ellipsize="marquee" /> + android:ellipsize="marquee" + android:visibility="gone" /> </LinearLayout> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 1a45b3a..cf08dea 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2095,6 +2095,10 @@ <enum name="hdpi" value="240" /> <!-- An extra high density screen, approximately 320dpi. --> <enum name="xhdpi" value="320" /> + <!-- An extra extra high density screen, approximately 480dpi. --> + <enum name="xxhdpi" value="480" /> + <!-- An extra extra extra high density screen, approximately 640dpi. --> + <enum name="xxxhdpi" value="640" /> </attr> </declare-styleable> diff --git a/docs/html/guide/topics/manifest/compatible-screens-element.jd b/docs/html/guide/topics/manifest/compatible-screens-element.jd index 3606b15..de921d2 100644 --- a/docs/html/guide/topics/manifest/compatible-screens-element.jd +++ b/docs/html/guide/topics/manifest/compatible-screens-element.jd @@ -9,7 +9,7 @@ parent.link=manifest-intro.html <pre> <<a href="#compatible-screens">compatible-screens</a>> <<a href="#screen">screen</a> android:<a href="#screenSize">screenSize</a>=["small" | "normal" | "large" | "xlarge"] - android:<a href="#screenDensity">screenDensity</a>=["ldpi" | "mdpi" | "hdpi" | "xhdpi"] /> + android:<a href="#screenDensity">screenDensity</a>=["ldpi" | "mdpi" | "hdpi" | "xhdpi" | "xxhdpi" | "xxxhdpi"] /> ... </compatible-screens> </pre> @@ -94,11 +94,9 @@ href="{@docRoot}guide/practices/screens_support.html#range">Supporting Multiple <li>{@code mdpi}</li> <li>{@code hdpi}</li> <li>{@code xhdpi}</li> + <li>{@code xxhdpi}</li> + <li>{@code xxxhdpi}</li> </ul> - <p class="note"><strong>Note:</strong> This attribute currently does not accept - {@code xxhdpi} as a valid value, but you can instead specify {@code 480} - as the value, which is the approximate threshold for xhdpi screens.</p> - <p>For information about the different screen densities, see <a href="{@docRoot}guide/practices/screens_support.html#range">Supporting Multiple Screens</a>.</p> </dd> @@ -110,8 +108,8 @@ href="{@docRoot}guide/practices/screens_support.html#range">Supporting Multiple <dt>example</dt> <dd> <p>If your application is compatible with only small and normal screens, regardless -of screen density, then you must specify eight different {@code <screen>} elements, -because each screen size has four different density configurations. You must declare each one of +of screen density, then you must specify twelve different {@code <screen>} elements, +because each screen size has six different density configurations. You must declare each one of these; any combination of size and density that you do <em>not</em> specify is considered a screen configuration with which your application is <em>not</em> compatible. Here's what the manifest entry looks like if your application is compatible with only small and normal screens:</p> @@ -125,11 +123,15 @@ entry looks like if your application is compatible with only small and normal sc <screen android:screenSize="small" android:screenDensity="mdpi" /> <screen android:screenSize="small" android:screenDensity="hdpi" /> <screen android:screenSize="small" android:screenDensity="xhdpi" /> + <screen android:screenSize="small" android:screenDensity="xxhdpi" /> + <screen android:screenSize="small" android:screenDensity="xxxhdpi" /> <!-- all normal size screens --> <screen android:screenSize="normal" android:screenDensity="ldpi" /> <screen android:screenSize="normal" android:screenDensity="mdpi" /> <screen android:screenSize="normal" android:screenDensity="hdpi" /> <screen android:screenSize="normal" android:screenDensity="xhdpi" /> + <screen android:screenSize="normal" android:screenDensity="xxhdpi" /> + <screen android:screenSize="normal" android:screenDensity="xxxhdpi" /> </compatible-screens> <application ... > ... diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index 7197dc0..266b0d9 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -169,6 +169,13 @@ public final class MidiManager { /** * Registers a callback to receive notifications when MIDI devices are added and removed. * + * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately + * for any devices that have open ports. This allows applications to know which input + * ports are already in use and, therefore, unavailable. + * + * Applications should call {@link #getDevices} before registering the callback + * to get a list of devices already added. + * * @param callback a {@link DeviceCallback} for MIDI device notifications * @param handler The {@link android.os.Handler Handler} that will be used for delivering the * device notifications. If handler is null, then the thread used for the @@ -288,7 +295,6 @@ public final class MidiManager { // fetch MidiDeviceInfo from the server MidiDeviceInfo deviceInfo = server.getDeviceInfo(); device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken); - sendOpenDeviceResponse(device, listenerF, handlerF); } catch (RemoteException e) { Log.e(TAG, "remote exception in getDeviceInfo()"); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 4e11070..6d07a57 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -40,6 +40,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.annotation.Nullable; import android.app.ActivityManagerNative; import android.app.AppGlobals; import android.app.AlertDialog; @@ -286,8 +287,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean mSystemReady; /** - * Id of the currently selected input method. + * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method. + * method. This is to be synchronized with the secure settings keyed with + * {@link Settings.Secure#DEFAULT_INPUT_METHOD}. + * + * <p>This can be transiently {@code null} when the system is re-initializing input method + * settings, e.g., the system locale is just changed.</p> + * + * <p>Note that {@link #mCurId} is used to track which IME is being connected to + * {@link InputMethodManagerService}.</p> + * + * @see #mCurId */ + @Nullable String mCurMethodId; /** @@ -317,9 +329,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub EditorInfo mCurAttribute; /** - * The input method ID of the input method service that we are currently + * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently * connected to or in the process of connecting to. + * + * <p>This can be {@code null} when no input method is connected.</p> + * + * @see #mCurMethodId */ + @Nullable String mCurId; /** @@ -967,7 +984,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub || (newLocale != null && !newLocale.equals(mLastSystemLocale))) { if (!updateOnlyWhenLocaleChanged) { hideCurrentInputLocked(0, null); - mCurMethodId = null; unbindCurrentMethodLocked(true, false); } if (DEBUG) { @@ -1523,7 +1539,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub channel.dispose(); } - void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) { + void unbindCurrentMethodLocked(boolean resetCurrentMethodAndClient, boolean savePosition) { + if (resetCurrentMethodAndClient) { + mCurMethodId = null; + } + if (mVisibleBound) { mContext.unbindService(mVisibleConnection); mVisibleBound = false; @@ -1550,9 +1570,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurId = null; clearCurMethodLocked(); - if (reportToClient && mCurClient != null) { - executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( - MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); + if (resetCurrentMethodAndClient) { + unbindCurrentClientLocked(); } } @@ -1903,13 +1922,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id)); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); - mCurMethodId = null; unbindCurrentMethodLocked(true, false); } mShortcutInputMethodsAndSubtypes.clear(); } else { // There is no longer an input method set, so stop any current one. - mCurMethodId = null; unbindCurrentMethodLocked(true, false); } // Here is not the perfect place to reset the switching controller. Ideally diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8e6e688..4756818 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8366,6 +8366,7 @@ public class PackageManagerService extends IPackageManager.Stub { final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); + boolean runtimePermissionsRevoked = false; int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY; boolean changedInstallPermission = false; @@ -8375,6 +8376,17 @@ public class PackageManagerService extends IPackageManager.Stub { if (!ps.isSharedUser()) { origPermissions = new PermissionsState(permissionsState); permissionsState.reset(); + } else { + // We need to know only about runtime permission changes since the + // calling code always writes the install permissions state but + // the runtime ones are written only if changed. The only cases of + // changed runtime permissions here are promotion of an install to + // runtime and revocation of a runtime from a shared user. + changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw( + ps.sharedUser, UserManagerService.getInstance().getUserIds()); + if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) { + runtimePermissionsRevoked = true; + } } } @@ -8590,9 +8602,11 @@ public class PackageManagerService extends IPackageManager.Stub { ps.installPermissionsFixed = true; } - // Persist the runtime permissions state for users with changes. + // Persist the runtime permissions state for users with changes. If permissions + // were revoked because no app in the shared user declares them we have to + // write synchronously to avoid losing runtime permissions state. for (int userId : changedRuntimePermissionUserIds) { - mSettings.writeRuntimePermissionsForUserLPr(userId, false); + mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked); } } @@ -12089,6 +12103,66 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) { + // Collect all used permissions in the UID + ArraySet<String> usedPermissions = new ArraySet<>(); + final int packageCount = su.packages.size(); + for (int i = 0; i < packageCount; i++) { + PackageSetting ps = su.packages.valueAt(i); + if (ps.pkg == null) { + continue; + } + final int requestedPermCount = ps.pkg.requestedPermissions.size(); + for (int j = 0; j < requestedPermCount; j++) { + String permission = ps.pkg.requestedPermissions.get(j); + BasePermission bp = mSettings.mPermissions.get(permission); + if (bp != null) { + usedPermissions.add(permission); + } + } + } + + PermissionsState permissionsState = su.getPermissionsState(); + // Prune install permissions + List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates(); + final int installPermCount = installPermStates.size(); + for (int i = installPermCount - 1; i >= 0; i--) { + PermissionState permissionState = installPermStates.get(i); + if (!usedPermissions.contains(permissionState.getName())) { + BasePermission bp = mSettings.mPermissions.get(permissionState.getName()); + if (bp != null) { + permissionsState.revokeInstallPermission(bp); + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); + } + } + } + + int[] runtimePermissionChangedUserIds = EmptyArray.INT; + + // Prune runtime permissions + for (int userId : allUserIds) { + List<PermissionState> runtimePermStates = permissionsState + .getRuntimePermissionStates(userId); + final int runtimePermCount = runtimePermStates.size(); + for (int i = runtimePermCount - 1; i >= 0; i--) { + PermissionState permissionState = runtimePermStates.get(i); + if (!usedPermissions.contains(permissionState.getName())) { + BasePermission bp = mSettings.mPermissions.get(permissionState.getName()); + if (bp != null) { + permissionsState.revokeRuntimePermission(bp, userId); + permissionsState.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, 0); + runtimePermissionChangedUserIds = ArrayUtils.appendInt( + runtimePermissionChangedUserIds, userId); + } + } + } + } + + return runtimePermissionChangedUserIds; + } + private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName, String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res, UserHandle user) { diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 3ecfd55..701e07f 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -575,6 +575,8 @@ public class MidiService extends IMidiManager.Stub { Client client = getClient(token); if (client == null) return; client.addListener(listener); + // Let listener know whether any ports are already busy. + updateStickyDeviceStatus(client.mUid, listener); } @Override @@ -584,6 +586,25 @@ public class MidiService extends IMidiManager.Stub { client.removeListener(listener); } + // Inform listener of the status of all known devices. + private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) { + synchronized (mDevicesByInfo) { + for (Device device : mDevicesByInfo.values()) { + // ignore private devices that our client cannot access + if (device.isUidAllowed(uid)) { + try { + MidiDeviceStatus status = device.getDeviceStatus(); + if (status != null) { + listener.onDeviceStatusChanged(status); + } + } catch (RemoteException e) { + Log.e(TAG, "remote exception", e); + } + } + } + } + } + private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0]; public MidiDeviceInfo[] getDevices() { |