diff options
Diffstat (limited to 'core/java/android')
40 files changed, 2316 insertions, 284 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 3dbb636..b197f90 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1916,6 +1916,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case HANG_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder who = data.readStrongBinder(); + boolean allowRestart = data.readInt() != 0; + hang(who, allowRestart); + reply.writeNoException(); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -4387,5 +4396,17 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public void hang(IBinder who, boolean allowRestart) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(who); + data.writeInt(allowRestart ? 1 : 0); + mRemote.transact(HANG_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index c9776f1..948210c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -95,8 +95,17 @@ public class AppOpsManager { public static final int OP_PLAY_AUDIO = 28; public static final int OP_READ_CLIPBOARD = 29; public static final int OP_WRITE_CLIPBOARD = 30; + public static final int OP_TAKE_MEDIA_BUTTONS = 31; + public static final int OP_TAKE_AUDIO_FOCUS = 32; + public static final int OP_AUDIO_MASTER_VOLUME = 33; + public static final int OP_AUDIO_VOICE_VOLUME = 34; + public static final int OP_AUDIO_RING_VOLUME = 35; + public static final int OP_AUDIO_MEDIA_VOLUME = 36; + public static final int OP_AUDIO_ALARM_VOLUME = 37; + public static final int OP_AUDIO_NOTIFICATION_VOLUME = 38; + public static final int OP_AUDIO_BLUETOOTH_VOLUME = 39; /** @hide */ - public static final int _NUM_OP = 31; + public static final int _NUM_OP = 40; /** * This maps each operation to the operation that serves as the @@ -138,6 +147,15 @@ public class AppOpsManager { OP_PLAY_AUDIO, OP_READ_CLIPBOARD, OP_WRITE_CLIPBOARD, + OP_TAKE_MEDIA_BUTTONS, + OP_TAKE_AUDIO_FOCUS, + OP_AUDIO_MASTER_VOLUME, + OP_AUDIO_VOICE_VOLUME, + OP_AUDIO_RING_VOLUME, + OP_AUDIO_MEDIA_VOLUME, + OP_AUDIO_ALARM_VOLUME, + OP_AUDIO_NOTIFICATION_VOLUME, + OP_AUDIO_BLUETOOTH_VOLUME, }; /** @@ -176,6 +194,15 @@ public class AppOpsManager { "PLAY_AUDIO", "READ_CLIPBOARD", "WRITE_CLIPBOARD", + "TAKE_MEDIA_BUTTONS", + "TAKE_AUDIO_FOCUS", + "AUDIO_MASTER_VOLUME", + "AUDIO_VOICE_VOLUME", + "AUDIO_RING_VOLUME", + "AUDIO_MEDIA_VOLUME", + "AUDIO_ALARM_VOLUME", + "AUDIO_NOTIFICATION_VOLUME", + "AUDIO_BLUETOOTH_VOLUME", }; /** @@ -214,6 +241,15 @@ public class AppOpsManager { null, // no permission for playing audio null, // no permission for reading clipboard null, // no permission for writing clipboard + null, // no permission for taking media buttons + null, // no permission for taking audio focus + null, // no permission for changing master volume + null, // no permission for changing voice volume + null, // no permission for changing ring volume + null, // no permission for changing media volume + null, // no permission for changing alarm volume + null, // no permission for changing notification volume + null, // no permission for changing bluetooth volume }; /** diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl index aca8305..952c900 100644 --- a/core/java/android/app/IActivityController.aidl +++ b/core/java/android/app/IActivityController.aidl @@ -58,4 +58,11 @@ interface IActivityController * immediately. */ int appNotResponding(String processName, int pid, String processStats); + + /** + * The system process watchdog has detected that the system seems to be + * hung. Return 1 to continue waiting, or -1 to let it continue with its + * normal kill. + */ + int systemNotResponding(String msg); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 5a798de..334a304 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -386,6 +386,8 @@ public interface IActivityManager extends IInterface { public void killUid(int uid, String reason) throws RemoteException; + public void hang(IBinder who, boolean allowRestart) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -654,10 +656,11 @@ public interface IActivityManager extends IInterface { int REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+162; int GET_LAUNCHED_FROM_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+163; int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164; - int CREATE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165; - int MOVE_TASK_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166; - int RESIZE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167; - int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168; - int GET_STACKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+169; - int SET_FOCUSED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170; + int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165; + int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166; + int CREATE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167; + int MOVE_TASK_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168; + int RESIZE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+169; + int GET_STACKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170; + int SET_FOCUSED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+171; } diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 4fbca73..3967740 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -633,7 +633,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * * @param id The identifier for this notification as per * {@link NotificationManager#notify(int, Notification) - * NotificationManager.notify(int, Notification)}. + * NotificationManager.notify(int, Notification)}; must not be 0. * @param notification The Notification to be displayed. * * @see #stopForeground(boolean) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 8352635..793736d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2355,6 +2355,8 @@ public class Intent implements Parcelable, Cloneable { * </p> * * @see #ACTION_IDLE_MAINTENANCE_END + * + * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_IDLE_MAINTENANCE_START = @@ -2382,6 +2384,8 @@ public class Intent implements Parcelable, Cloneable { * by the system. * * @see #ACTION_IDLE_MAINTENANCE_START + * + * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_IDLE_MAINTENANCE_END = diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 217cf76..283a097 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -109,6 +109,7 @@ public class RestrictionEntry implements Parcelable { */ public RestrictionEntry(String key, String selectedString) { this.key = key; + this.type = TYPE_CHOICE; this.currentValue = selectedString; } @@ -119,6 +120,7 @@ public class RestrictionEntry implements Parcelable { */ public RestrictionEntry(String key, boolean selectedState) { this.key = key; + this.type = TYPE_BOOLEAN; setSelectedState(selectedState); } @@ -129,6 +131,7 @@ public class RestrictionEntry implements Parcelable { */ public RestrictionEntry(String key, String[] selectedStrings) { this.key = key; + this.type = TYPE_MULTI_SELECT; this.currentValues = selectedStrings; } diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java new file mode 100644 index 0000000..1c2db47 --- /dev/null +++ b/core/java/android/content/UndoManager.java @@ -0,0 +1,932 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelableParcel; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Top-level class for managing and interacting with the global undo state for + * a document or application. This class supports both undo and redo and has + * helpers for merging undoable operations together as they are performed. + * + * <p>A single undoable operation is represented by {@link UndoOperation} which + * apps implement to define their undo/redo behavior. The UndoManager keeps + * a stack of undo states; each state can have one or more undo operations + * inside of it.</p> + * + * <p>Updates to the stack must be done inside of a {@link #beginUpdate}/{@link #endUpdate()} + * pair. During this time you can add new operations to the stack with + * {@link #addOperation}, retrieve and modify existing operations with + * {@link #getLastOperation}, control the label shown to the user for this operation + * with {@link #setUndoLabel} and {@link #suggestUndoLabel}, etc.</p> + * + * <p>Every {link UndoOperation} is associated with an {@link UndoOwner}, which identifies + * the data it belongs to. The owner is used to indicate how operations are dependent + * on each other -- operations with the same owner are dependent on others with the + * same owner. For example, you may have a document with multiple embedded objects. If the + * document itself and each embedded object use different owners, then you + * can provide undo semantics appropriate to the user's context: while within + * an embedded object, only edits to that object are seen and the user can + * undo/redo them without needing to impact edits in other objects; while + * within the larger document, all edits can be seen and the user must + * undo/redo them as a single stream.</p> + */ +public class UndoManager { + private final HashMap<String, UndoOwner> mOwners = new HashMap<String, UndoOwner>(); + private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>(); + private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>(); + private int mUpdateCount; + private int mHistorySize = 20; + private UndoState mWorking; + private int mCommitId = 1; + private boolean mInUndo; + private boolean mMerged; + + private int mStateSeq; + private int mNextSavedIdx; + private UndoOwner[] mStateOwners; + + /** + * Never merge with the last undo state. + */ + public static final int MERGE_MODE_NONE = 0; + + /** + * Allow merge with the last undo state only if it contains + * operations with the caller's owner. + */ + public static final int MERGE_MODE_UNIQUE = 1; + + /** + * Always allow merge with the last undo state, if possible. + */ + public static final int MERGE_MODE_ANY = 2; + + public UndoOwner getOwner(String tag, Object data) { + if (tag == null) { + throw new NullPointerException("tag can't be null"); + } + if (data == null) { + throw new NullPointerException("data can't be null"); + } + UndoOwner owner = mOwners.get(tag); + if (owner != null) { + if (owner.mData != data) { + if (owner.mData != null) { + throw new IllegalStateException("Owner " + owner + " already exists with data " + + owner.mData + " but giving different data " + data); + } + owner.mData = data; + } + return owner; + } + + owner = new UndoOwner(tag); + owner.mManager = this; + owner.mData = data; + mOwners.put(tag, owner); + return owner; + } + + void removeOwner(UndoOwner owner) { + // XXX need to figure out how to prune. + if (false) { + mOwners.remove(owner.mTag); + owner.mManager = null; + } + } + + /** + * Flatten the current undo state into a Parcelable object, which can later be restored + * with {@link #restoreInstanceState(android.os.Parcelable)}. + */ + public Parcelable saveInstanceState() { + if (mUpdateCount > 0) { + throw new IllegalStateException("Can't save state while updating"); + } + ParcelableParcel pp = new ParcelableParcel(getClass().getClassLoader()); + Parcel p = pp.getParcel(); + mStateSeq++; + if (mStateSeq <= 0) { + mStateSeq = 0; + } + mNextSavedIdx = 0; + p.writeInt(mHistorySize); + p.writeInt(mOwners.size()); + // XXX eventually we need to be smart here about limiting the + // number of undo states we write to not exceed X bytes. + int i = mUndos.size(); + while (i > 0) { + p.writeInt(1); + i--; + mUndos.get(i).writeToParcel(p); + } + i = mRedos.size(); + p.writeInt(i); + while (i > 0) { + p.writeInt(2); + i--; + mRedos.get(i).writeToParcel(p); + } + p.writeInt(0); + return pp; + } + + void saveOwner(UndoOwner owner, Parcel out) { + if (owner.mStateSeq == mStateSeq) { + out.writeInt(owner.mSavedIdx); + } else { + owner.mStateSeq = mStateSeq; + owner.mSavedIdx = mNextSavedIdx; + out.writeInt(owner.mSavedIdx); + out.writeString(owner.mTag); + mNextSavedIdx++; + } + } + + /** + * Restore an undo state previously created with {@link #saveInstanceState()}. This will + * restore the UndoManager's state to almost exactly what it was at the point it had + * been previously saved; the only information not restored is the data object + * associated with each {@link UndoOwner}, which requires separate calls to + * {@link #getOwner(String, Object)} to re-associate the owner with its data. + */ + public void restoreInstanceState(Parcelable state) { + if (mUpdateCount > 0) { + throw new IllegalStateException("Can't save state while updating"); + } + forgetUndos(null, -1); + forgetRedos(null, -1); + ParcelableParcel pp = (ParcelableParcel)state; + Parcel p = pp.getParcel(); + mHistorySize = p.readInt(); + mStateOwners = new UndoOwner[p.readInt()]; + + int stype; + while ((stype=p.readInt()) != 0) { + UndoState ustate = new UndoState(this, p, pp.getClassLoader()); + if (stype == 1) { + mUndos.add(0, ustate); + } else { + mRedos.add(0, ustate); + } + } + } + + UndoOwner restoreOwner(Parcel in) { + int idx = in.readInt(); + UndoOwner owner = mStateOwners[idx]; + if (owner == null) { + String tag = in.readString(); + owner = new UndoOwner(tag); + mStateOwners[idx] = owner; + mOwners.put(tag, owner); + } + return owner; + } + + /** + * Set the maximum number of undo states that will be retained. + */ + public void setHistorySize(int size) { + mHistorySize = size; + if (mHistorySize >= 0 && countUndos(null) > mHistorySize) { + forgetUndos(null, countUndos(null) - mHistorySize); + } + } + + /** + * Return the current maximum number of undo states. + */ + public int getHistorySize() { + return mHistorySize; + } + + /** + * Perform undo of last/top <var>count</var> undo states. The states impacted + * by this can be limited through <var>owners</var>. + * @param owners Optional set of owners that should be impacted. If null, all + * undo states will be visible and available for undo. If non-null, only those + * states that contain one of the owners specified here will be visible. + * @param count Number of undo states to pop. + * @return Returns the number of undo states that were actually popped. + */ + public int undo(UndoOwner[] owners, int count) { + if (mWorking != null) { + throw new IllegalStateException("Can't be called during an update"); + } + + int num = 0; + int i = -1; + + mInUndo = true; + + UndoState us = getTopUndo(null); + if (us != null) { + us.makeExecuted(); + } + + while (count > 0 && (i=findPrevState(mUndos, owners, i)) >= 0) { + UndoState state = mUndos.remove(i); + state.undo(); + mRedos.add(state); + count--; + num++; + } + + mInUndo = false; + + return num; + } + + /** + * Perform redo of last/top <var>count</var> undo states in the transient redo stack. + * The states impacted by this can be limited through <var>owners</var>. + * @param owners Optional set of owners that should be impacted. If null, all + * undo states will be visible and available for undo. If non-null, only those + * states that contain one of the owners specified here will be visible. + * @param count Number of undo states to pop. + * @return Returns the number of undo states that were actually redone. + */ + public int redo(UndoOwner[] owners, int count) { + if (mWorking != null) { + throw new IllegalStateException("Can't be called during an update"); + } + + int num = 0; + int i = -1; + + mInUndo = true; + + while (count > 0 && (i=findPrevState(mRedos, owners, i)) >= 0) { + UndoState state = mRedos.remove(i); + state.redo(); + mUndos.add(state); + count--; + num++; + } + + mInUndo = false; + + return num; + } + + /** + * Returns true if we are currently inside of an undo/redo operation. This is + * useful for editors to know whether they should be generating new undo state + * when they see edit operations happening. + */ + public boolean isInUndo() { + return mInUndo; + } + + public int forgetUndos(UndoOwner[] owners, int count) { + if (count < 0) { + count = mUndos.size(); + } + + int removed = 0; + for (int i=0; i<mUndos.size() && removed < count; i++) { + UndoState state = mUndos.get(i); + if (count > 0 && matchOwners(state, owners)) { + state.destroy(); + mUndos.remove(i); + removed++; + } + } + + return removed; + } + + public int forgetRedos(UndoOwner[] owners, int count) { + if (count < 0) { + count = mRedos.size(); + } + + int removed = 0; + for (int i=0; i<mRedos.size() && removed < count; i++) { + UndoState state = mRedos.get(i); + if (count > 0 && matchOwners(state, owners)) { + state.destroy(); + mRedos.remove(i); + removed++; + } + } + + return removed; + } + + /** + * Return the number of undo states on the undo stack. + * @param owners If non-null, only those states containing an operation with one of + * the owners supplied here will be counted. + */ + public int countUndos(UndoOwner[] owners) { + if (owners == null) { + return mUndos.size(); + } + + int count=0; + int i=0; + while ((i=findNextState(mUndos, owners, i)) >= 0) { + count++; + i++; + } + return count; + } + + /** + * Return the number of redo states on the undo stack. + * @param owners If non-null, only those states containing an operation with one of + * the owners supplied here will be counted. + */ + public int countRedos(UndoOwner[] owners) { + if (owners == null) { + return mRedos.size(); + } + + int count=0; + int i=0; + while ((i=findNextState(mRedos, owners, i)) >= 0) { + count++; + i++; + } + return count; + } + + /** + * Return the user-visible label for the top undo state on the stack. + * @param owners If non-null, will select the top-most undo state containing an + * operation with one of the owners supplied here. + */ + public CharSequence getUndoLabel(UndoOwner[] owners) { + UndoState state = getTopUndo(owners); + return state != null ? state.getLabel() : null; + } + + /** + * Return the user-visible label for the top redo state on the stack. + * @param owners If non-null, will select the top-most undo state containing an + * operation with one of the owners supplied here. + */ + public CharSequence getRedoLabel(UndoOwner[] owners) { + UndoState state = getTopRedo(owners); + return state != null ? state.getLabel() : null; + } + + /** + * Start creating a new undo state. Multiple calls to this function will nest until + * they are all matched by a later call to {@link #endUpdate}. + * @param label Optional user-visible label for this new undo state. + */ + public void beginUpdate(CharSequence label) { + if (mInUndo) { + throw new IllegalStateException("Can't being update while performing undo/redo"); + } + if (mUpdateCount <= 0) { + createWorkingState(); + mMerged = false; + mUpdateCount = 0; + } + + mWorking.updateLabel(label); + mUpdateCount++; + } + + private void createWorkingState() { + mWorking = new UndoState(this, mCommitId++); + if (mCommitId < 0) { + mCommitId = 1; + } + } + + /** + * Returns true if currently inside of a {@link #beginUpdate}. + */ + public boolean isInUpdate() { + return mUpdateCount > 0; + } + + /** + * Forcibly set a new for the new undo state being built within a {@link #beginUpdate}. + * Any existing label will be replaced with this one. + */ + public void setUndoLabel(CharSequence label) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + mWorking.setLabel(label); + } + + /** + * Set a new for the new undo state being built within a {@link #beginUpdate}, but + * only if there is not a label currently set for it. + */ + public void suggestUndoLabel(CharSequence label) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + mWorking.updateLabel(label); + } + + /** + * Return the number of times {@link #beginUpdate} has been called without a matching + * {@link #endUpdate} call. + */ + public int getUpdateNestingLevel() { + return mUpdateCount; + } + + /** + * Check whether there is an {@link UndoOperation} in the current {@link #beginUpdate} + * undo state. + * @param owner Optional owner of the operation to look for. If null, will succeed + * if there is any operation; if non-null, will only succeed if there is an operation + * with the given owner. + * @return Returns true if there is a matching operation in the current undo state. + */ + public boolean hasOperation(UndoOwner owner) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + return mWorking.hasOperation(owner); + } + + /** + * Return the most recent {@link UndoOperation} that was added to the update. + * @param mergeMode May be either {@link #MERGE_MODE_NONE} or {@link #MERGE_MODE_ANY}. + */ + public UndoOperation<?> getLastOperation(int mergeMode) { + return getLastOperation(null, null, mergeMode); + } + + /** + * Return the most recent {@link UndoOperation} that was added to the update and + * has the given owner. + * @param owner Optional owner of last operation to retrieve. If null, the last + * operation regardless of owner will be retrieved; if non-null, the last operation + * matching the given owner will be retrieved. + * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, + * or {@link #MERGE_MODE_ANY}. + */ + public UndoOperation<?> getLastOperation(UndoOwner owner, int mergeMode) { + return getLastOperation(null, owner, mergeMode); + } + + /** + * Return the most recent {@link UndoOperation} that was added to the update and + * has the given owner. + * @param clazz Optional class of the last operation to retrieve. If null, the + * last operation regardless of class will be retrieved; if non-null, the last + * operation whose class is the same as the given class will be retrieved. + * @param owner Optional owner of last operation to retrieve. If null, the last + * operation regardless of owner will be retrieved; if non-null, the last operation + * matching the given owner will be retrieved. + * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, + * or {@link #MERGE_MODE_ANY}. + */ + public <T extends UndoOperation> T getLastOperation(Class<T> clazz, UndoOwner owner, + int mergeMode) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) { + UndoState state = getTopUndo(null); + UndoOperation<?> last; + if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners()) + && state.canMerge() && (last=state.getLastOperation(clazz, owner)) != null) { + if (last.allowMerge()) { + mWorking.destroy(); + mWorking = state; + mUndos.remove(state); + mMerged = true; + return (T)last; + } + } + } + + return mWorking.getLastOperation(clazz, owner); + } + + /** + * Add a new UndoOperation to the current update. + * @param op The new operation to add. + * @param mergeMode May be either {@link #MERGE_MODE_NONE}, {@link #MERGE_MODE_UNIQUE}, + * or {@link #MERGE_MODE_ANY}. + */ + public void addOperation(UndoOperation<?> op, int mergeMode) { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + UndoOwner owner = op.getOwner(); + if (owner.mManager != this) { + throw new IllegalArgumentException( + "Given operation's owner is not in this undo manager."); + } + if (mergeMode != MERGE_MODE_NONE && !mMerged && !mWorking.hasData()) { + UndoState state = getTopUndo(null); + if (state != null && (mergeMode == MERGE_MODE_ANY || !state.hasMultipleOwners()) + && state.canMerge() && state.hasOperation(op.getOwner())) { + mWorking.destroy(); + mWorking = state; + mUndos.remove(state); + mMerged = true; + } + } + mWorking.addOperation(op); + } + + /** + * Finish the creation of an undo state, matching a previous call to + * {@link #beginUpdate}. + */ + public void endUpdate() { + if (mWorking == null) { + throw new IllegalStateException("Must be called during an update"); + } + mUpdateCount--; + + if (mUpdateCount == 0) { + pushWorkingState(); + } + } + + private void pushWorkingState() { + int N = mUndos.size() + 1; + + if (mWorking.hasData()) { + mUndos.add(mWorking); + forgetRedos(null, -1); + mWorking.commit(); + if (N >= 2) { + // The state before this one can no longer be merged, ever. + // The only way to get back to it is for the user to perform + // an undo. + mUndos.get(N-2).makeExecuted(); + } + } else { + mWorking.destroy(); + } + mWorking = null; + + if (mHistorySize >= 0 && N > mHistorySize) { + forgetUndos(null, N - mHistorySize); + } + } + + /** + * Commit the last finished undo state. This undo state can no longer be + * modified with further {@link #MERGE_MODE_UNIQUE} or + * {@link #MERGE_MODE_ANY} merge modes. If called while inside of an update, + * this will push any changes in the current update on to the undo stack + * and result with a fresh undo state, behaving as if {@link #endUpdate()} + * had been called enough to unwind the current update, then the last state + * committed, and {@link #beginUpdate} called to restore the update nesting. + * @param owner The optional owner to determine whether to perform the commit. + * If this is non-null, the commit will only execute if the current top undo + * state contains an operation with the given owner. + * @return Returns an integer identifier for the committed undo state, which + * can later be used to try to uncommit the state to perform further edits on it. + */ + public int commitState(UndoOwner owner) { + if (mWorking != null && mWorking.hasData()) { + if (owner == null || mWorking.hasOperation(owner)) { + mWorking.setCanMerge(false); + int commitId = mWorking.getCommitId(); + pushWorkingState(); + createWorkingState(); + mMerged = true; + return commitId; + } + } else { + UndoState state = getTopUndo(null); + if (state != null && (owner == null || state.hasOperation(owner))) { + state.setCanMerge(false); + return state.getCommitId(); + } + } + return -1; + } + + /** + * Attempt to undo a previous call to {@link #commitState}. This will work + * if the undo state at the top of the stack has the given id, and has not been + * involved in an undo operation. Otherwise false is returned. + * @param commitId The identifier for the state to be uncommitted, as returned + * by {@link #commitState}. + * @param owner Optional owner that must appear in the committed state. + * @return Returns true if the uncommit is successful, else false. + */ + public boolean uncommitState(int commitId, UndoOwner owner) { + if (mWorking != null && mWorking.getCommitId() == commitId) { + if (owner == null || mWorking.hasOperation(owner)) { + return mWorking.setCanMerge(true); + } + } else { + UndoState state = getTopUndo(null); + if (state != null && (owner == null || state.hasOperation(owner))) { + if (state.getCommitId() == commitId) { + return state.setCanMerge(true); + } + } + } + return false; + } + + UndoState getTopUndo(UndoOwner[] owners) { + if (mUndos.size() <= 0) { + return null; + } + int i = findPrevState(mUndos, owners, -1); + return i >= 0 ? mUndos.get(i) : null; + } + + UndoState getTopRedo(UndoOwner[] owners) { + if (mRedos.size() <= 0) { + return null; + } + int i = findPrevState(mRedos, owners, -1); + return i >= 0 ? mRedos.get(i) : null; + } + + boolean matchOwners(UndoState state, UndoOwner[] owners) { + if (owners == null) { + return true; + } + for (int i=0; i<owners.length; i++) { + if (state.matchOwner(owners[i])) { + return true; + } + } + return false; + } + + int findPrevState(ArrayList<UndoState> states, UndoOwner[] owners, int from) { + final int N = states.size(); + + if (from == -1) { + from = N-1; + } + if (from >= N) { + return -1; + } + if (owners == null) { + return from; + } + + while (from >= 0) { + UndoState state = states.get(from); + if (matchOwners(state, owners)) { + return from; + } + from--; + } + + return -1; + } + + int findNextState(ArrayList<UndoState> states, UndoOwner[] owners, int from) { + final int N = states.size(); + + if (from < 0) { + from = 0; + } + if (from >= N) { + return -1; + } + if (owners == null) { + return from; + } + + while (from < N) { + UndoState state = states.get(from); + if (matchOwners(state, owners)) { + return from; + } + from++; + } + + return -1; + } + + final static class UndoState { + private final UndoManager mManager; + private final int mCommitId; + private final ArrayList<UndoOperation<?>> mOperations = new ArrayList<UndoOperation<?>>(); + private ArrayList<UndoOperation<?>> mRecent; + private CharSequence mLabel; + private boolean mCanMerge = true; + private boolean mExecuted; + + UndoState(UndoManager manager, int commitId) { + mManager = manager; + mCommitId = commitId; + } + + UndoState(UndoManager manager, Parcel p, ClassLoader loader) { + mManager = manager; + mCommitId = p.readInt(); + mCanMerge = p.readInt() != 0; + mExecuted = p.readInt() != 0; + mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); + final int N = p.readInt(); + for (int i=0; i<N; i++) { + UndoOwner owner = mManager.restoreOwner(p); + UndoOperation op = (UndoOperation)p.readParcelable(loader); + op.mOwner = owner; + mOperations.add(op); + } + } + + void writeToParcel(Parcel p) { + if (mRecent != null) { + throw new IllegalStateException("Can't save state before committing"); + } + p.writeInt(mCommitId); + p.writeInt(mCanMerge ? 1 : 0); + p.writeInt(mExecuted ? 1 : 0); + TextUtils.writeToParcel(mLabel, p, 0); + final int N = mOperations.size(); + p.writeInt(N); + for (int i=0; i<N; i++) { + UndoOperation op = mOperations.get(i); + mManager.saveOwner(op.mOwner, p); + p.writeParcelable(op, 0); + } + } + + int getCommitId() { + return mCommitId; + } + + void setLabel(CharSequence label) { + mLabel = label; + } + + void updateLabel(CharSequence label) { + if (mLabel != null) { + mLabel = label; + } + } + + CharSequence getLabel() { + return mLabel; + } + + boolean setCanMerge(boolean state) { + // Don't allow re-enabling of merging if state has been executed. + if (state && mExecuted) { + return false; + } + mCanMerge = state; + return true; + } + + void makeExecuted() { + mExecuted = true; + } + + boolean canMerge() { + return mCanMerge && !mExecuted; + } + + int countOperations() { + return mOperations.size(); + } + + boolean hasOperation(UndoOwner owner) { + final int N = mOperations.size(); + if (owner == null) { + return N != 0; + } + for (int i=0; i<N; i++) { + if (mOperations.get(i).getOwner() == owner) { + return true; + } + } + return false; + } + + boolean hasMultipleOwners() { + final int N = mOperations.size(); + if (N <= 1) { + return false; + } + UndoOwner owner = mOperations.get(0).getOwner(); + for (int i=1; i<N; i++) { + if (mOperations.get(i).getOwner() != owner) { + return true; + } + } + return false; + } + + void addOperation(UndoOperation<?> op) { + if (mOperations.contains(op)) { + throw new IllegalStateException("Already holds " + op); + } + mOperations.add(op); + if (mRecent == null) { + mRecent = new ArrayList<UndoOperation<?>>(); + mRecent.add(op); + } + op.mOwner.mOpCount++; + } + + <T extends UndoOperation> T getLastOperation(Class<T> clazz, UndoOwner owner) { + final int N = mOperations.size(); + if (clazz == null && owner == null) { + return N > 0 ? (T)mOperations.get(N-1) : null; + } + // First look for the top-most operation with the same owner. + for (int i=N-1; i>=0; i--) { + UndoOperation<?> op = mOperations.get(i); + if (owner != null && op.getOwner() != owner) { + continue; + } + // Return this operation if it has the same class that the caller wants. + // Note that we don't search deeper for the class, because we don't want + // to end up with a different order of operations for the same owner. + if (clazz != null && op.getClass() != clazz) { + return null; + } + return (T)op; + } + + return null; + } + + boolean matchOwner(UndoOwner owner) { + for (int i=mOperations.size()-1; i>=0; i--) { + if (mOperations.get(i).matchOwner(owner)) { + return true; + } + } + return false; + } + + boolean hasData() { + for (int i=mOperations.size()-1; i>=0; i--) { + if (mOperations.get(i).hasData()) { + return true; + } + } + return false; + } + + void commit() { + final int N = mRecent != null ? mRecent.size() : 0; + for (int i=0; i<N; i++) { + mRecent.get(i).commit(); + } + mRecent = null; + } + + void undo() { + for (int i=mOperations.size()-1; i>=0; i--) { + mOperations.get(i).undo(); + } + } + + void redo() { + final int N = mOperations.size(); + for (int i=0; i<N; i++) { + mOperations.get(i).redo(); + } + } + + void destroy() { + for (int i=mOperations.size()-1; i>=0; i--) { + UndoOwner owner = mOperations.get(i).mOwner; + owner.mOpCount--; + if (owner.mOpCount <= 0) { + if (owner.mOpCount < 0) { + throw new IllegalStateException("Underflow of op count on owner " + owner + + " in op " + mOperations.get(i)); + } + mManager.removeOwner(owner); + } + } + } + } +} diff --git a/core/java/android/content/UndoOperation.java b/core/java/android/content/UndoOperation.java new file mode 100644 index 0000000..8084b1f --- /dev/null +++ b/core/java/android/content/UndoOperation.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A single undoable operation. You must subclass this to implement the state + * and behavior for your operation. Instances of this class are placed and + * managed in an {@link UndoManager}. + */ +public abstract class UndoOperation<DATA> implements Parcelable { + UndoOwner mOwner; + + /** + * Create a new instance of the operation. + * @param owner Who owns the data being modified by this undo state; must be + * returned by {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}. + */ + public UndoOperation(UndoOwner owner) { + mOwner = owner; + } + + /** + * Construct from a Parcel. + */ + protected UndoOperation(Parcel src, ClassLoader loader) { + } + + /** + * Owning object as given to {@link #UndoOperation(UndoOwner)}. + */ + public UndoOwner getOwner() { + return mOwner; + } + + /** + * Synonym for {@link #getOwner()}.{@link android.content.UndoOwner#getData()}. + */ + public DATA getOwnerData() { + return (DATA)mOwner.getData(); + } + + /** + * Return true if this undo operation is a member of the given owner. + * The default implementation is <code>owner == getOwner()</code>. You + * can override this to provide more sophisticated dependencies between + * owners. + */ + public boolean matchOwner(UndoOwner owner) { + return owner == getOwner(); + } + + /** + * Return true if this operation actually contains modification data. The + * default implementation always returns true. If you return false, the + * operation will be dropped when the final undo state is being built. + */ + public boolean hasData() { + return true; + } + + /** + * Return true if this operation can be merged with a later operation. + * The default implementation always returns true. + */ + public boolean allowMerge() { + return true; + } + + /** + * Called when this undo state is being committed to the undo stack. + * The implementation should perform the initial edits and save any state that + * may be needed to undo them. + */ + public abstract void commit(); + + /** + * Called when this undo state is being popped off the undo stack (in to + * the temporary redo stack). The implementation should remove the original + * edits and thus restore the target object to its prior value. + */ + public abstract void undo(); + + /** + * Called when this undo state is being pushed back from the transient + * redo stack to the main undo stack. The implementation should re-apply + * the edits that were previously removed by {@link #undo}. + */ + public abstract void redo(); + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/content/UndoOwner.java b/core/java/android/content/UndoOwner.java new file mode 100644 index 0000000..a279de6 --- /dev/null +++ b/core/java/android/content/UndoOwner.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +/** + * Representation of an owner of {@link UndoOperation} objects in an {@link UndoManager}. + */ +public class UndoOwner { + final String mTag; + + UndoManager mManager; + Object mData; + int mOpCount; + + // For saving/restoring state. + int mStateSeq; + int mSavedIdx; + + UndoOwner(String tag) { + mTag = tag; + } + + /** + * Return the unique tag name identifying this owner. This is the tag + * supplied to {@link UndoManager#getOwner(String, Object) UndoManager.getOwner} + * and is immutable. + */ + public String getTag() { + return mTag; + } + + /** + * Return the actual data object of the owner. This is the data object + * supplied to {@link UndoManager#getOwner(String, Object) UndoManager.getOwner}. An + * owner may have a null data if it was restored from a previously saved state with + * no getOwner call to associate it with its data. + */ + public Object getData() { + return mData; + } +} diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 64eaf9b..1b997f0 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -293,7 +293,10 @@ public class PackageParser { pi.sharedUserLabel = p.mSharedUserLabel; pi.applicationInfo = generateApplicationInfo(p, flags, state, userId); pi.installLocation = p.installLocation; - pi.requiredForAllUsers = p.mRequiredForAllUsers; + if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 + || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + pi.requiredForAllUsers = p.mRequiredForAllUsers; + } pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; pi.firstInstallTime = firstInstallTime; @@ -1893,11 +1896,12 @@ public class PackageParser { false)) { ai.flags |= ApplicationInfo.FLAG_PERSISTENT; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers, - false)) { - owner.mRequiredForAllUsers = true; - } + } + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers, + false)) { + owner.mRequiredForAllUsers = true; } String restrictedAccountType = sa.getString(com.android.internal.R.styleable diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 42f4faf..c7976c3 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,8 +16,6 @@ package android.content.res; -import android.os.Trace; -import android.view.View; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -30,6 +28,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable.ConstantState; import android.os.Build; import android.os.Bundle; +import android.os.Trace; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -1985,6 +1984,13 @@ public class Resources { } } + /** + * @hide + */ + public LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() { + return sPreloadedDrawables[0]; + } + private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying, int resourceId, String name) { // We allow preloading of resources even if they vary by font scale (which diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 4f59e8e..725a1ff 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -30,9 +30,9 @@ import android.util.Log; import android.util.LruCache; import android.util.Printer; -import java.sql.Date; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.Map; import java.util.regex.Pattern; diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java index e3362a7..9823c49 100644 --- a/core/java/android/hardware/location/GeofenceHardwareImpl.java +++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java @@ -470,11 +470,12 @@ public final class GeofenceHardwareImpl { synchronized (mGeofences) { callback = mGeofences.get(geofenceId); } - if (callback == null) return; - try { - callback.onGeofenceAdd(geofenceId, msg.arg2); - } catch (RemoteException e) {Log.i(TAG, "Remote Exception:" + e);} + if (callback != null) { + try { + callback.onGeofenceAdd(geofenceId, msg.arg2); + } catch (RemoteException e) {Log.i(TAG, "Remote Exception:" + e);} + } releaseWakeLock(); break; case REMOVE_GEOFENCE_CALLBACK: @@ -482,13 +483,14 @@ public final class GeofenceHardwareImpl { synchronized (mGeofences) { callback = mGeofences.get(geofenceId); } - if (callback == null) return; - try { - callback.onGeofenceRemove(geofenceId, msg.arg2); - } catch (RemoteException e) {} - synchronized (mGeofences) { - mGeofences.remove(geofenceId); + if (callback != null) { + try { + callback.onGeofenceRemove(geofenceId, msg.arg2); + } catch (RemoteException e) {} + synchronized (mGeofences) { + mGeofences.remove(geofenceId); + } } releaseWakeLock(); break; @@ -498,11 +500,12 @@ public final class GeofenceHardwareImpl { synchronized (mGeofences) { callback = mGeofences.get(geofenceId); } - if (callback == null) return; - try { - callback.onGeofencePause(geofenceId, msg.arg2); - } catch (RemoteException e) {} + if (callback != null) { + try { + callback.onGeofencePause(geofenceId, msg.arg2); + } catch (RemoteException e) {} + } releaseWakeLock(); break; @@ -511,11 +514,12 @@ public final class GeofenceHardwareImpl { synchronized (mGeofences) { callback = mGeofences.get(geofenceId); } - if (callback == null) return; - try { - callback.onGeofenceResume(geofenceId, msg.arg2); - } catch (RemoteException e) {} + if (callback != null) { + try { + callback.onGeofenceResume(geofenceId, msg.arg2); + } catch (RemoteException e) {} + } releaseWakeLock(); break; @@ -530,12 +534,14 @@ public final class GeofenceHardwareImpl { " Transition: " + geofenceTransition.mTransition + " Location: " + geofenceTransition.mLocation + ":" + mGeofences); - try { - callback.onGeofenceTransition( - geofenceTransition.mGeofenceId, geofenceTransition.mTransition, - geofenceTransition.mLocation, geofenceTransition.mTimestamp, - GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE); - } catch (RemoteException e) {} + if (callback != null) { + try { + callback.onGeofenceTransition( + geofenceTransition.mGeofenceId, geofenceTransition.mTransition, + geofenceTransition.mLocation, geofenceTransition.mTimestamp, + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE); + } catch (RemoteException e) {} + } releaseWakeLock(); break; case GEOFENCE_CALLBACK_BINDER_DIED: @@ -572,16 +578,16 @@ public final class GeofenceHardwareImpl { available = (val == GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE ? true : false); callbackList = mCallbacks[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE]; - if (callbackList == null) return; - - if (DEBUG) Log.d(TAG, "MonitoringSystemChangeCallback: GPS : " + available); - - for (IGeofenceHardwareMonitorCallback c: callbackList) { - try { - c.onMonitoringSystemChange( - GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, available, - location); - } catch (RemoteException e) {} + if (callbackList != null) { + if (DEBUG) Log.d(TAG, "MonitoringSystemChangeCallback: GPS : " + available); + + for (IGeofenceHardwareMonitorCallback c: callbackList) { + try { + c.onMonitoringSystemChange( + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, available, + location); + } catch (RemoteException e) {} + } } releaseWakeLock(); break; diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index 08ba728..9c3e405 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -306,10 +306,9 @@ public final class NsdManager { switch (message.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - mConnected.countDown(); break; case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: - // Ignore + mConnected.countDown(); break; case AsyncChannel.CMD_CHANNEL_DISCONNECTED: Log.e(TAG, "Channel lost"); diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index e9e7551..7ffd30b 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -49,6 +49,11 @@ public class Binder implements IBinder { private static final boolean FIND_POTENTIAL_LEAKS = false; private static final String TAG = "Binder"; + /** + * Control whether dump() calls are allowed. + */ + private static String sDumpDisabled = null; + /* mObject is used by native code, do not remove or rename */ private int mObject; private IInterface mOwner; @@ -224,7 +229,23 @@ public class Binder implements IBinder { } return null; } - + + /** + * Control disabling of dump calls in this process. This is used by the system + * process watchdog to disable incoming dump calls while it has detecting the system + * is hung and is reporting that back to the activity controller. This is to + * prevent the controller from getting hung up on bug reports at this point. + * @hide + * + * @param msg The message to show instead of the dump; if null, dumps are + * re-enabled. + */ + public static void setDumpDisabled(String msg) { + synchronized (Binder.class) { + sDumpDisabled = msg; + } + } + /** * Default implementation is a stub that returns false. You will want * to override this to do the appropriate unmarshalling of transactions. @@ -269,7 +290,15 @@ public class Binder implements IBinder { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new PrintWriter(fout); try { - dump(fd, pw, args); + final String disabled; + synchronized (Binder.class) { + disabled = sDumpDisabled; + } + if (disabled == null) { + dump(fd, pw, args); + } else { + pw.println(sDumpDisabled); + } } finally { pw.flush(); } diff --git a/core/java/android/os/ParcelableParcel.java b/core/java/android/os/ParcelableParcel.java new file mode 100644 index 0000000..11785f1 --- /dev/null +++ b/core/java/android/os/ParcelableParcel.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * Parcelable containing a raw Parcel of data. + * @hide + */ +public class ParcelableParcel implements Parcelable { + final Parcel mParcel; + final ClassLoader mClassLoader; + + public ParcelableParcel(ClassLoader loader) { + mParcel = Parcel.obtain(); + mClassLoader = loader; + } + + public ParcelableParcel(Parcel src, ClassLoader loader) { + mParcel = Parcel.obtain(); + mClassLoader = loader; + int size = src.readInt(); + int pos = src.dataPosition(); + mParcel.appendFrom(src, src.dataPosition(), size); + src.setDataPosition(pos + size); + } + + public Parcel getParcel() { + mParcel.setDataPosition(0); + return mParcel; + } + + public ClassLoader getClassLoader() { + return mClassLoader; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mParcel.dataSize()); + dest.appendFrom(mParcel, 0, mParcel.dataSize()); + } + + public static final Parcelable.ClassLoaderCreator<ParcelableParcel> CREATOR + = new Parcelable.ClassLoaderCreator<ParcelableParcel>() { + public ParcelableParcel createFromParcel(Parcel in) { + return new ParcelableParcel(in, null); + } + + public ParcelableParcel createFromParcel(Parcel in, ClassLoader loader) { + return new ParcelableParcel(in, loader); + } + + public ParcelableParcel[] newArray(int size) { + return new ParcelableParcel[size]; + } + }; +} diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 3307a8c..e53cb5e 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -65,6 +65,8 @@ public final class Trace { public static final long TRACE_TAG_APP = 1L << 12; /** @hide */ public static final long TRACE_TAG_RESOURCES = 1L << 13; + /** @hide */ + public static final long TRACE_TAG_DALVIK = 1L << 14; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9164aa6..163709a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4084,6 +4084,13 @@ public final class Settings { public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; /** + * Whether or not to enable the dial pad autocomplete functionality. + * + * @hide + */ + public static final String DIALPAD_AUTOCOMPLETE = "dialpad_autocomplete"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -4123,7 +4130,8 @@ public final class Settings { MOUNT_UMS_AUTOSTART, MOUNT_UMS_PROMPT, MOUNT_UMS_NOTIFY_ENABLED, - UI_NIGHT_MODE + UI_NIGHT_MODE, + DIALPAD_AUTOCOMPLETE }; /** @@ -5481,7 +5489,6 @@ public final class Settings { WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, - WIFI_SCAN_ALWAYS_AVAILABLE, WIFI_NUM_OPEN_NETWORKS_KEPT, EMERGENCY_TONE, CALL_AUTO_RETRY, diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 73d400e..578a86e 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -42,6 +42,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.MissingResourceException; import java.util.Set; /** @@ -1128,9 +1129,23 @@ public class TextToSpeech { if (loc == null) { return LANG_NOT_SUPPORTED; } - String language = loc.getISO3Language(); - String country = loc.getISO3Country(); + String language = null, country = null; + try { + language = loc.getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } + + try { + country = loc.getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } + String variant = loc.getVariant(); + // Check if the language, country, variant are available, and cache // the available parts. // Note that the language is not actually set here, instead it is cached so it @@ -1195,8 +1210,23 @@ public class TextToSpeech { return runAction(new Action<Integer>() { @Override public Integer run(ITextToSpeechService service) throws RemoteException { - return service.isLanguageAvailable(loc.getISO3Language(), - loc.getISO3Country(), loc.getVariant()); + String language = null, country = null; + + try { + language = loc.getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } + + try { + country = loc.getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e); + return LANG_NOT_SUPPORTED; + } + + return service.isLanguageAvailable(language, country, loc.getVariant()); } }, LANG_NOT_SUPPORTED, "isLanguageAvailable"); } diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java index aff233d..3855ff3 100644 --- a/core/java/android/text/method/LinkMovementMethod.java +++ b/core/java/android/text/method/LinkMovementMethod.java @@ -36,6 +36,11 @@ public class LinkMovementMethod extends ScrollingMovementMethod { private static final int DOWN = 3; @Override + public boolean canSelectArbitrarily() { + return true; + } + + @Override protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event) { switch (keyCode) { diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index b2988ed..c2c247e 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -172,6 +172,7 @@ public class FocusFinder { try { // Note: This sort is stable. mSequentialFocusComparator.setRoot(root); + mSequentialFocusComparator.setIsLayoutRtl(root.isLayoutRtl()); Collections.sort(focusables, mSequentialFocusComparator); } finally { mSequentialFocusComparator.recycle(); @@ -180,9 +181,9 @@ public class FocusFinder { final int count = focusables.size(); switch (direction) { case View.FOCUS_FORWARD: - return getForwardFocusable(root, focused, focusables, count); + return getNextFocusable(focused, focusables, count); case View.FOCUS_BACKWARD: - return getBackwardFocusable(root, focused, focusables, count); + return getPreviousFocusable(focused, focusables, count); } return focusables.get(count - 1); } @@ -239,13 +240,6 @@ public class FocusFinder { return closest; } - private static View getForwardFocusable(ViewGroup root, View focused, - ArrayList<View> focusables, int count) { - return (root.isLayoutRtl()) ? - getPreviousFocusable(focused, focusables, count) : - getNextFocusable(focused, focusables, count); - } - private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) { if (focused != null) { int position = focusables.lastIndexOf(focused); @@ -259,13 +253,6 @@ public class FocusFinder { return null; } - private static View getBackwardFocusable(ViewGroup root, View focused, - ArrayList<View> focusables, int count) { - return (root.isLayoutRtl()) ? - getNextFocusable(focused, focusables, count) : - getPreviousFocusable(focused, focusables, count); - } - private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) { if (focused != null) { int position = focusables.indexOf(focused); @@ -619,6 +606,7 @@ public class FocusFinder { private final Rect mFirstRect = new Rect(); private final Rect mSecondRect = new Rect(); private ViewGroup mRoot; + private boolean mIsLayoutRtl; public void recycle() { mRoot = null; @@ -628,6 +616,10 @@ public class FocusFinder { mRoot = root; } + public void setIsLayoutRtl(boolean b) { + mIsLayoutRtl = b; + } + public int compare(View first, View second) { if (first == second) { return 0; @@ -641,17 +633,17 @@ public class FocusFinder { } else if (mFirstRect.top > mSecondRect.top) { return 1; } else if (mFirstRect.left < mSecondRect.left) { - return -1; + return mIsLayoutRtl ? 1 : -1; } else if (mFirstRect.left > mSecondRect.left) { - return 1; + return mIsLayoutRtl ? -1 : 1; } else if (mFirstRect.bottom < mSecondRect.bottom) { return -1; } else if (mFirstRect.bottom > mSecondRect.bottom) { return 1; } else if (mFirstRect.right < mSecondRect.right) { - return -1; + return mIsLayoutRtl ? 1 : -1; } else if (mFirstRect.right > mSecondRect.right) { - return 1; + return mIsLayoutRtl ? -1 : 1; } else { // The view are distinct but completely coincident so we consider // them equal for our purposes. Since the sort is stable, this diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 2ec9a7d..cc7d948 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -21,6 +21,7 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.DrawFilter; import android.graphics.Matrix; +import android.graphics.NinePatch; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; @@ -273,6 +274,18 @@ class GLES20Canvas extends HardwareCanvas { private static native int nGetStencilSize(); + void setCountOverdrawEnabled(boolean enabled) { + nSetCountOverdrawEnabled(mRenderer, enabled); + } + + static native void nSetCountOverdrawEnabled(int renderer, boolean enabled); + + float getOverdraw() { + return nGetOverdraw(mRenderer); + } + + static native float nGetOverdraw(int renderer); + /////////////////////////////////////////////////////////////////////////// // Functor /////////////////////////////////////////////////////////////////////////// @@ -314,21 +327,21 @@ class GLES20Canvas extends HardwareCanvas { * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_LAYERS = 0; + static final int FLUSH_CACHES_LAYERS = 0; /** * Must match Caches::FlushMode values * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_MODERATE = 1; + static final int FLUSH_CACHES_MODERATE = 1; /** * Must match Caches::FlushMode values * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_FULL = 2; + static final int FLUSH_CACHES_FULL = 2; /** * Flush caches to reclaim as much memory as possible. The amount of memory @@ -338,10 +351,8 @@ class GLES20Canvas extends HardwareCanvas { * {@link #FLUSH_CACHES_FULL}. * * @param level Hint about the amount of memory to reclaim - * - * @hide */ - public static void flushCaches(int level) { + static void flushCaches(int level) { nFlushCaches(level); } @@ -353,21 +364,28 @@ class GLES20Canvas extends HardwareCanvas { * * @hide */ - public static void terminateCaches() { + static void terminateCaches() { nTerminateCaches(); } private static native void nTerminateCaches(); - /** - * @hide - */ - public static void initCaches() { - nInitCaches(); + static boolean initCaches() { + return nInitCaches(); } - private static native void nInitCaches(); - + private static native boolean nInitCaches(); + + /////////////////////////////////////////////////////////////////////////// + // Atlas + /////////////////////////////////////////////////////////////////////////// + + static void initAtlas(GraphicBuffer buffer, int[] map) { + nInitAtlas(buffer, map, map.length); + } + + private static native void nInitAtlas(GraphicBuffer buffer, int[] map, int count); + /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -718,20 +736,21 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + Bitmap bitmap = patch.getBitmap(); if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing patches int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks, + nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); } } - private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks, + private static native void nDrawPatch(int renderer, int bitmap, byte[] chunks, float left, float top, float right, float bottom, int paint); @Override @@ -741,14 +760,14 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint); + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmap( - int renderer, int bitmap, byte[] buffer, float left, float top, int paint); + private static native void nDrawBitmap(int renderer, int bitmap, + float left, float top, int paint); @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { @@ -757,15 +776,13 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, - matrix.native_instance, nativePaint); + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff, - int matrix, int paint); + private static native void nDrawBitmap(int renderer, int bitmap, int matrix, int paint); @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { @@ -787,7 +804,7 @@ class GLES20Canvas extends HardwareCanvas { bottom = src.bottom; } - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); @@ -814,14 +831,14 @@ class GLES20Canvas extends HardwareCanvas { bottom = src.bottom; } - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer, + private static native void nDrawBitmap(int renderer, int bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float left, float top, float right, float bottom, int paint); @@ -891,14 +908,14 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight, + nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmapMesh(int renderer, int bitmap, byte[] buffer, + private static native void nDrawBitmapMesh(int renderer, int bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, int paint); diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 3272504..d367267 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -18,6 +18,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Matrix; +import android.graphics.NinePatch; import java.util.ArrayList; @@ -29,7 +30,8 @@ class GLES20DisplayList extends DisplayList { // alive as long as the DisplayList is alive. The Bitmap and DisplayList lists // are populated by the GLES20RecordingCanvas during appropriate drawing calls and are // cleared at the start of a new drawing frame or when the view is detached from the window. - final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5); + final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(10); + final ArrayList<NinePatch> mNinePatches = new ArrayList<NinePatch>(10); final ArrayList<DisplayList> mChildDisplayLists = new ArrayList<DisplayList>(); private GLES20RecordingCanvas mCanvas; @@ -83,7 +85,12 @@ class GLES20DisplayList extends DisplayList { } mValid = false; + clearReferences(); + } + + void clearReferences() { mBitmaps.clear(); + mNinePatches.clear(); mChildDisplayLists.clear(); } diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index 7da2451..ec059d5 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -19,6 +19,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Matrix; +import android.graphics.NinePatch; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -62,8 +63,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { } void start() { - mDisplayList.mBitmaps.clear(); - mDisplayList.mChildDisplayLists.clear(); + mDisplayList.clearReferences(); } int end(int nativeDisplayList) { @@ -80,9 +80,10 @@ class GLES20RecordingCanvas extends GLES20Canvas { } @Override - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { - super.drawPatch(bitmap, chunks, dst, paint); - mDisplayList.mBitmaps.add(bitmap); + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + super.drawPatch(patch, dst, paint); + mDisplayList.mBitmaps.add(patch.getBitmap()); + mDisplayList.mNinePatches.add(patch); // Shaders in the Paint are ignored when drawing a Bitmap } diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java index 685dc70..68ba77c 100644 --- a/core/java/android/view/GLES20RenderLayer.java +++ b/core/java/android/view/GLES20RenderLayer.java @@ -100,12 +100,17 @@ class GLES20RenderLayer extends GLES20Layer { @Override HardwareCanvas start(Canvas currentCanvas) { + return start(currentCanvas, null); + } + + @Override + HardwareCanvas start(Canvas currentCanvas, Rect dirty) { if (currentCanvas instanceof GLES20Canvas) { ((GLES20Canvas) currentCanvas).interrupt(); } HardwareCanvas canvas = getCanvas(); canvas.setViewport(mWidth, mHeight); - canvas.onPreDraw(null); + canvas.onPreDraw(dirty); return canvas; } diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java index e863e49..a4cc630 100644 --- a/core/java/android/view/GLES20TextureLayer.java +++ b/core/java/android/view/GLES20TextureLayer.java @@ -63,6 +63,11 @@ class GLES20TextureLayer extends GLES20Layer { } @Override + HardwareCanvas start(Canvas currentCanvas, Rect dirty) { + return null; + } + + @Override void end(Canvas currentCanvas) { } diff --git a/core/java/android/view/GraphicBuffer.aidl b/core/java/android/view/GraphicBuffer.aidl new file mode 100644 index 0000000..6dc6bed --- /dev/null +++ b/core/java/android/view/GraphicBuffer.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +parcelable GraphicBuffer; diff --git a/core/java/android/view/GraphicBuffer.java b/core/java/android/view/GraphicBuffer.java new file mode 100644 index 0000000..b4576f3 --- /dev/null +++ b/core/java/android/view/GraphicBuffer.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Simple wrapper for the native GraphicBuffer class. + * + * @hide + */ +@SuppressWarnings("UnusedDeclaration") +public class GraphicBuffer implements Parcelable { + // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h + public static final int USAGE_SW_READ_NEVER = 0x0; + public static final int USAGE_SW_READ_RARELY = 0x2; + public static final int USAGE_SW_READ_OFTEN = 0x3; + public static final int USAGE_SW_READ_MASK = 0xF; + + public static final int USAGE_SW_WRITE_NEVER = 0x0; + public static final int USAGE_SW_WRITE_RARELY = 0x20; + public static final int USAGE_SW_WRITE_OFTEN = 0x30; + public static final int USAGE_SW_WRITE_MASK = 0xF0; + + public static final int USAGE_SOFTWARE_MASK = USAGE_SW_READ_MASK | USAGE_SW_WRITE_MASK; + + public static final int USAGE_PROTECTED = 0x4000; + + public static final int USAGE_HW_TEXTURE = 0x100; + public static final int USAGE_HW_RENDER = 0x200; + public static final int USAGE_HW_2D = 0x400; + public static final int USAGE_HW_COMPOSER = 0x800; + public static final int USAGE_HW_VIDEO_ENCODER = 0x10000; + public static final int USAGE_HW_MASK = 0x71F00; + + private final int mWidth; + private final int mHeight; + private final int mFormat; + private final int mUsage; + // Note: do not rename, this field is used by native code + private final int mNativeObject; + + // These two fields are only used by lock/unlockCanvas() + private Canvas mCanvas; + private int mSaveCount; + + /** + * Creates new <code>GraphicBuffer</code> instance. This method will return null + * if the buffer cannot be created. + * + * @param width The width in pixels of the buffer + * @param height The height in pixels of the buffer + * @param format The format of each pixel as specified in {@link PixelFormat} + * @param usage Hint indicating how the buffer will be used + * + * @return A <code>GraphicBuffer</code> instance or null + */ + public static GraphicBuffer create(int width, int height, int format, int usage) { + int nativeObject = nCreateGraphicBuffer(width, height, format, usage); + if (nativeObject != 0) { + return new GraphicBuffer(width, height, format, usage, nativeObject); + } + return null; + } + + /** + * Private use only. See {@link #create(int, int, int, int)}. + */ + private GraphicBuffer(int width, int height, int format, int usage, int nativeObject) { + mWidth = width; + mHeight = height; + mFormat = format; + mUsage = usage; + mNativeObject = nativeObject; + } + + /** + * Returns the width of this buffer in pixels. + */ + public int getWidth() { + return mWidth; + } + + /** + * Returns the height of this buffer in pixels. + */ + public int getHeight() { + return mHeight; + } + + /** + * Returns the pixel format of this buffer. The pixel format must be one of + * the formats defined in {@link PixelFormat}. + */ + public int getFormat() { + return mFormat; + } + + /** + * Returns the usage hint set on this buffer. + */ + public int getUsage() { + return mUsage; + } + + /** + * <p>Start editing the pixels in the buffer. A null is returned if the buffer + * cannot be locked for editing.</p> + * + * <p>The content of the buffer is preserved between unlockCanvas() + * and lockCanvas().</p> + * + * @return A Canvas used to draw into the buffer, or null. + * + * @see #lockCanvas(android.graphics.Rect) + * @see #unlockCanvasAndPost(android.graphics.Canvas) + */ + public Canvas lockCanvas() { + return lockCanvas(null); + } + + /** + * Just like {@link #lockCanvas()} but allows specification of a dirty + * rectangle. + * + * @param dirty Area of the buffer that may be modified. + + * @return A Canvas used to draw into the surface or null + * + * @see #lockCanvas() + * @see #unlockCanvasAndPost(android.graphics.Canvas) + */ + public Canvas lockCanvas(Rect dirty) { + if (mCanvas == null) { + mCanvas = new Canvas(); + } + + if (nLockCanvas(mNativeObject, mCanvas, dirty)) { + mSaveCount = mCanvas.save(); + return mCanvas; + } + + return null; + } + + /** + * Finish editing pixels in the buffer. + * + * @param canvas The Canvas previously returned by lockCanvas() + * + * @see #lockCanvas() + * @see #lockCanvas(android.graphics.Rect) + */ + public void unlockCanvasAndPost(Canvas canvas) { + if (mCanvas != null && canvas == mCanvas) { + canvas.restoreToCount(mSaveCount); + mSaveCount = 0; + + nUnlockCanvasAndPost(mNativeObject, mCanvas); + } + } + + @Override + protected void finalize() throws Throwable { + try { + nDestroyGraphicBuffer(mNativeObject); + } finally { + super.finalize(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mFormat); + dest.writeInt(mUsage); + nWriteGraphicBufferToParcel(mNativeObject, dest); + } + + public static final Parcelable.Creator<GraphicBuffer> CREATOR = + new Parcelable.Creator<GraphicBuffer>() { + public GraphicBuffer createFromParcel(Parcel in) { + int width = in.readInt(); + int height = in.readInt(); + int format = in.readInt(); + int usage = in.readInt(); + int nativeObject = nReadGraphicBufferFromParcel(in); + if (nativeObject != 0) { + return new GraphicBuffer(width, height, format, usage, nativeObject); + } + return null; + } + + public GraphicBuffer[] newArray(int size) { + return new GraphicBuffer[size]; + } + }; + + private static native int nCreateGraphicBuffer(int width, int height, int format, int usage); + private static native void nDestroyGraphicBuffer(int nativeObject); + private static native void nWriteGraphicBufferToParcel(int nativeObject, Parcel dest); + private static native int nReadGraphicBufferFromParcel(Parcel in); + private static native boolean nLockCanvas(int nativeObject, Canvas canvas, Rect dirty); + private static native boolean nUnlockCanvasAndPost(int nativeObject, Canvas canvas); +} diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 18b838b..23383d9 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -158,14 +158,22 @@ abstract class HardwareLayer { /** * This must be invoked before drawing onto this layer. * - * @param currentCanvas + * @param currentCanvas The canvas whose rendering needs to be interrupted */ abstract HardwareCanvas start(Canvas currentCanvas); /** + * This must be invoked before drawing onto this layer. + * + * @param dirty The dirty area to repaint + * @param currentCanvas The canvas whose rendering needs to be interrupted + */ + abstract HardwareCanvas start(Canvas currentCanvas, Rect dirty); + + /** * This must be invoked after drawing onto this layer. * - * @param currentCanvas + * @param currentCanvas The canvas whose rendering needs to be resumed */ abstract void end(Canvas currentCanvas); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 8308459..e8c1653 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -17,6 +17,7 @@ package android.view; import android.content.ComponentCallbacks2; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; @@ -24,7 +25,10 @@ import android.opengl.EGL14; import android.opengl.GLUtils; import android.opengl.ManagedEGLContext; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -42,7 +46,6 @@ import javax.microedition.khronos.opengles.GL; import java.io.File; import java.io.PrintWriter; -import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; import static javax.microedition.khronos.egl.EGL10.*; @@ -162,15 +165,32 @@ public abstract class HardwareRenderer { "debug.hwui.show_layers_updates"; /** - * Turn on to show overdraw level. + * Controls overdraw debugging. * * Possible values: - * "true", to enable overdraw debugging * "false", to disable overdraw debugging + * "show", to show overdraw areas on screen + * "count", to display an overdraw counter * * @hide */ - public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw"; + public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw"; + + /** + * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this + * value, overdraw will be shown on screen by coloring pixels. + * + * @hide + */ + public static final String OVERDRAW_PROPERTY_SHOW = "show"; + + /** + * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this + * value, an overdraw counter will be shown on screen. + * + * @hide + */ + public static final String OVERDRAW_PROPERTY_COUNT = "count"; /** * Turn on to debug non-rectangular clip operations. @@ -762,6 +782,17 @@ public abstract class HardwareRenderer { private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; private static final int PROFILE_DRAW_DP_PER_MS = 7; + private static final String[] VISUALIZERS = { + PROFILE_PROPERTY_VISUALIZE_BARS, + PROFILE_PROPERTY_VISUALIZE_LINES + }; + + private static final String[] OVERDRAW = { + OVERDRAW_PROPERTY_SHOW, + OVERDRAW_PROPERTY_COUNT + }; + private static final int OVERDRAW_TYPE_COUNT = 1; + static EGL10 sEgl; static EGLDisplay sEglDisplay; static EGLConfig sEglConfig; @@ -807,7 +838,9 @@ public abstract class HardwareRenderer { Paint mProfilePaint; boolean mDebugDirtyRegions; - boolean mShowOverdraw; + int mDebugOverdraw = -1; + HardwareLayer mDebugOverdrawLayer; + Paint mDebugOverdrawPaint; final int mGlVersion; final boolean mTranslucent; @@ -826,18 +859,13 @@ public abstract class HardwareRenderer { loadSystemProperties(null); } - private static final String[] VISUALIZERS = { - PROFILE_PROPERTY_VISUALIZE_BARS, - PROFILE_PROPERTY_VISUALIZE_LINES - }; - @Override boolean loadSystemProperties(Surface surface) { boolean value; boolean changed = false; String profiling = SystemProperties.get(PROFILE_PROPERTY); - int graphType = Arrays.binarySearch(VISUALIZERS, profiling); + int graphType = search(VISUALIZERS, profiling); value = graphType >= 0; if (graphType != mProfileVisualizerType) { @@ -894,11 +922,19 @@ public abstract class HardwareRenderer { } } - value = SystemProperties.getBoolean( - HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false); - if (value != mShowOverdraw) { + String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); + int debugOverdraw = search(OVERDRAW, overdraw); + if (debugOverdraw != mDebugOverdraw) { changed = true; - mShowOverdraw = value; + mDebugOverdraw = debugOverdraw; + + if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) { + if (mDebugOverdrawLayer != null) { + mDebugOverdrawLayer.destroy(); + mDebugOverdrawLayer = null; + mDebugOverdrawPaint = null; + } + } } if (nLoadProperties()) { @@ -908,6 +944,13 @@ public abstract class HardwareRenderer { return changed; } + private static int search(String[] values, String value) { + for (int i = 0; i < values.length; i++) { + if (values[i].equals(value)) return i; + } + return -1; + } + @Override void dumpGfxInfo(PrintWriter pw) { if (mProfileEnabled) { @@ -968,7 +1011,7 @@ public abstract class HardwareRenderer { if (fallback) { // we'll try again if it was context lost setRequested(false); - Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + "Switching back to software rendering."); } } @@ -976,7 +1019,7 @@ public abstract class HardwareRenderer { @Override boolean initialize(Surface surface) throws Surface.OutOfResourcesException { if (isRequested() && !isEnabled()) { - initializeEgl(); + boolean contextCreated = initializeEgl(); mGl = createEglSurface(surface); mDestroyed = false; @@ -991,6 +1034,10 @@ public abstract class HardwareRenderer { mCanvas.setName(mName); } setEnabled(true); + + if (contextCreated) { + initAtlas(); + } } return mCanvas != null; @@ -1010,7 +1057,7 @@ public abstract class HardwareRenderer { abstract int[] getConfig(boolean dirtyRegions); - void initializeEgl() { + boolean initializeEgl() { synchronized (sEglLock) { if (sEgl == null && sEglConfig == null) { sEgl = (EGL10) EGLContext.getEGL(); @@ -1043,7 +1090,10 @@ public abstract class HardwareRenderer { if (mEglContext == null) { mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); sEglContextStorage.set(createManagedContext(mEglContext)); + return true; } + + return false; } private EGLConfig loadEglConfig() { @@ -1181,6 +1231,7 @@ public abstract class HardwareRenderer { } abstract void initCaches(); + abstract void initAtlas(); EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; @@ -1193,6 +1244,7 @@ public abstract class HardwareRenderer { "Could not create an EGL context. eglCreateContext failed with error: " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } + return context; } @@ -1380,6 +1432,8 @@ public abstract class HardwareRenderer { canvas.restoreToCount(saveCount); view.mRecreateDisplayList = false; + debugOverdraw(attachInfo, dirty, canvas, displayList); + mFrameCount++; debugDirtyRegions(dirty, canvas); @@ -1399,6 +1453,61 @@ public abstract class HardwareRenderer { } } + abstract void countOverdraw(HardwareCanvas canvas); + abstract float getOverdraw(HardwareCanvas canvas); + + private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty, + HardwareCanvas canvas, DisplayList displayList) { + + if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) { + // TODO: Use an alpha layer allocated from a GraphicBuffer + // The alpha format will help with rendering performance and + // the GraphicBuffer will let us skip the read pixels step + if (mDebugOverdrawLayer == null) { + mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true); + } else if (mDebugOverdrawLayer.getWidth() != mWidth || + mDebugOverdrawLayer.getHeight() != mHeight) { + mDebugOverdrawLayer.resize(mWidth, mHeight); + } + + if (!mDebugOverdrawLayer.isValid()) { + mDebugOverdraw = -1; + return; + } + + HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty); + countOverdraw(layerCanvas); + final int restoreCount = layerCanvas.save(); + layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN); + layerCanvas.restoreToCount(restoreCount); + mDebugOverdrawLayer.end(canvas); + + float overdraw = getOverdraw(layerCanvas); + DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics(); + + drawOverdrawCounter(canvas, overdraw, metrics.density); + } + } + + private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) { + final String text = String.format("%.2fx", overdraw); + final Paint paint = setupPaint(density); + // HSBtoColor will clamp the values in the 0..1 range + paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f)); + + canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint); + } + + private Paint setupPaint(float density) { + if (mDebugOverdrawPaint == null) { + mDebugOverdrawPaint = new Paint(); + mDebugOverdrawPaint.setAntiAlias(true); + mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000); + mDebugOverdrawPaint.setTextSize(density * 20.0f); + } + return mDebugOverdrawPaint; + } + private DisplayList buildDisplayList(View view, HardwareCanvas canvas) { view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED; @@ -1788,7 +1897,29 @@ public abstract class HardwareRenderer { @Override void initCaches() { - GLES20Canvas.initCaches(); + if (GLES20Canvas.initCaches()) { + // Caches were (re)initialized, rebind atlas + initAtlas(); + } + } + + @Override + void initAtlas() { + IBinder binder = ServiceManager.getService("assetatlas"); + if (binder == null) return; + + IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); + try { + GraphicBuffer buffer = atlas.getBuffer(); + if (buffer != null) { + int[] map = atlas.getMap(); + if (map != null) { + GLES20Canvas.initAtlas(buffer, map); + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Could not acquire atlas", e); + } } @Override @@ -1985,6 +2116,16 @@ public abstract class HardwareRenderer { } @Override + void countOverdraw(HardwareCanvas canvas) { + ((GLES20Canvas) canvas).setCountOverdrawEnabled(true); + } + + @Override + float getOverdraw(HardwareCanvas canvas) { + return ((GLES20Canvas) canvas).getOverdraw(); + } + + @Override public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { return ((GLES20TextureLayer) layer).getSurfaceTexture(); } diff --git a/core/java/android/view/IAssetAtlas.aidl b/core/java/android/view/IAssetAtlas.aidl new file mode 100644 index 0000000..2595179 --- /dev/null +++ b/core/java/android/view/IAssetAtlas.aidl @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.GraphicBuffer; + +/** + * Programming interface to the system assets atlas. This atlas, when + * present, holds preloaded drawable in a single, shareable graphics + * buffer. This allows multiple processes to share the same data to + * save up on memory. + * + * @hide + */ +interface IAssetAtlas { + /** + * Returns the atlas buffer (texture) or null if the atlas is + * not available yet. + */ + GraphicBuffer getBuffer(); + + /** + * Returns the map of the bitmaps stored in the atlas or null + * if the atlas is not available yet. + * + * Each bitmap is represented by several entries in the array: + * int0: SkBitmap*, the native bitmap object + * int1: x position + * int2: y position + * int3: rotated, 1 if the bitmap must be rotated, 0 otherwise + */ + int[] getMap(); +} diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 4989c3a..e0786f7 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -34,19 +34,21 @@ public class Surface implements Parcelable { private static native int nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) throws OutOfResourcesException; + private static native int nativeCreateFromSurfaceControl(int surfaceControlNativeObject); - private native Canvas nativeLockCanvas(int nativeObject, Rect dirty); - private native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas); + private static native void nativeLockCanvas(int nativeObject, Canvas canvas, Rect dirty) + throws OutOfResourcesException; + private static native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas); private static native void nativeRelease(int nativeObject); private static native boolean nativeIsValid(int nativeObject); private static native boolean nativeIsConsumerRunningBehind(int nativeObject); - private static native int nativeCopyFrom(int nativeObject, int surfaceControlNativeObject); private static native int nativeReadFromParcel(int nativeObject, Parcel source); private static native void nativeWriteToParcel(int nativeObject, Parcel dest); public static final Parcelable.Creator<Surface> CREATOR = new Parcelable.Creator<Surface>() { + @Override public Surface createFromParcel(Parcel source) { try { Surface s = new Surface(); @@ -57,26 +59,20 @@ public class Surface implements Parcelable { return null; } } + + @Override public Surface[] newArray(int size) { return new Surface[size]; } }; private final CloseGuard mCloseGuard = CloseGuard.get(); - private String mName; - // Note: These fields are accessed by native code. - // The mSurfaceControl will only be present for Surfaces used by the window - // server or system processes. When this class is parceled we defer to the - // mSurfaceControl to do the parceling. Otherwise we parcel the - // mNativeSurface. + // Guarded state. + final Object mLock = new Object(); // protects the native state + private String mName; int mNativeObject; // package scope only for SurfaceControl access - - // protects the native state - private final Object mNativeObjectLock = new Object(); - - private int mGenerationId; // incremented each time mNativeSurface changes - @SuppressWarnings("UnusedDeclaration") + private int mGenerationId; // incremented each time mNativeObject changes private final Canvas mCanvas = new CompatibleCanvas(); // A matrix to scale the matrix set by application. This is set to null for @@ -125,21 +121,22 @@ public class Surface implements Parcelable { throw new IllegalArgumentException("surfaceTexture must not be null"); } - mName = surfaceTexture.toString(); - try { - mNativeObject = nativeCreateFromSurfaceTexture(surfaceTexture); - } catch (OutOfResourcesException ex) { - // We can't throw OutOfResourcesException because it would be an API change. - throw new RuntimeException(ex); + synchronized (mLock) { + mName = surfaceTexture.toString(); + try { + setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture)); + } catch (OutOfResourcesException ex) { + // We can't throw OutOfResourcesException because it would be an API change. + throw new RuntimeException(ex); + } } - - mCloseGuard.open("release"); } /* called from android_view_Surface_createFromIGraphicBufferProducer() */ private Surface(int nativeObject) { - mNativeObject = nativeObject; - mCloseGuard.open("release"); + synchronized (mLock) { + setNativeObjectLocked(nativeObject); + } } @Override @@ -160,13 +157,11 @@ public class Surface implements Parcelable { * This will make the surface invalid. */ public void release() { - synchronized (mNativeObjectLock) { + synchronized (mLock) { if (mNativeObject != 0) { nativeRelease(mNativeObject); - mNativeObject = 0; - mGenerationId++; + setNativeObjectLocked(0); } - mCloseGuard.close(); } } @@ -187,7 +182,7 @@ public class Surface implements Parcelable { * Otherwise returns false. */ public boolean isValid() { - synchronized (mNativeObjectLock) { + synchronized (mLock) { if (mNativeObject == 0) return false; return nativeIsValid(mNativeObject); } @@ -201,7 +196,9 @@ public class Surface implements Parcelable { * @hide */ public int getGenerationId() { - return mGenerationId; + synchronized (mLock) { + return mGenerationId; + } } /** @@ -211,7 +208,7 @@ public class Surface implements Parcelable { * @hide */ public boolean isConsumerRunningBehind() { - synchronized (mNativeObjectLock) { + synchronized (mLock) { checkNotReleasedLocked(); return nativeIsConsumerRunningBehind(mNativeObject); } @@ -234,9 +231,10 @@ public class Surface implements Parcelable { */ public Canvas lockCanvas(Rect inOutDirty) throws OutOfResourcesException, IllegalArgumentException { - synchronized (mNativeObjectLock) { + synchronized (mLock) { checkNotReleasedLocked(); - return nativeLockCanvas(mNativeObject, inOutDirty); + nativeLockCanvas(mNativeObject, mCanvas, inOutDirty); + return mCanvas; } } @@ -247,7 +245,12 @@ public class Surface implements Parcelable { * @param canvas The canvas previously obtained from {@link #lockCanvas}. */ public void unlockCanvasAndPost(Canvas canvas) { - synchronized (mNativeObjectLock) { + if (canvas != mCanvas) { + throw new IllegalArgumentException("canvas object must be the same instance that " + + "was previously returned by lockCanvas"); + } + + synchronized (mLock) { checkNotReleasedLocked(); nativeUnlockCanvasAndPost(mNativeObject, canvas); } @@ -273,7 +276,6 @@ public class Surface implements Parcelable { } } - /** * Copy another surface to this one. This surface now holds a reference * to the same data as the original surface, and is -not- the owner. @@ -287,22 +289,24 @@ public class Surface implements Parcelable { if (other == null) { throw new IllegalArgumentException("other must not be null"); } - if (other.mNativeObject == 0) { + + int surfaceControlPtr = other.mNativeObject; + if (surfaceControlPtr == 0) { throw new NullPointerException( "SurfaceControl native object is null. Are you using a released SurfaceControl?"); } - synchronized (mNativeObjectLock) { - mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject); - if (mNativeObject == 0) { - // nativeCopyFrom released our reference - mCloseGuard.close(); + int newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr); + + synchronized (mLock) { + if (mNativeObject != 0) { + nativeRelease(mNativeObject); } - mGenerationId++; + setNativeObjectLocked(newNativeObject); } } /** - * This is intended to be used by {@link SurfaceView.updateWindow} only. + * This is intended to be used by {@link SurfaceView#updateWindow} only. * @param other access is not thread safe * @hide * @deprecated @@ -313,21 +317,18 @@ public class Surface implements Parcelable { throw new IllegalArgumentException("other must not be null"); } if (other != this) { - synchronized (mNativeObjectLock) { + final int newPtr; + synchronized (other.mLock) { + newPtr = other.mNativeObject; + other.setNativeObjectLocked(0); + } + + synchronized (mLock) { if (mNativeObject != 0) { - // release our reference to our native object nativeRelease(mNativeObject); } - // transfer the reference from other to us - if (other.mNativeObject != 0 && mNativeObject == 0) { - mCloseGuard.open("release"); - } - mNativeObject = other.mNativeObject; - mGenerationId++; + setNativeObjectLocked(newPtr); } - other.mNativeObject = 0; - other.mGenerationId++; - other.mCloseGuard.close(); } } @@ -340,14 +341,10 @@ public class Surface implements Parcelable { if (source == null) { throw new IllegalArgumentException("source must not be null"); } - synchronized (mNativeObjectLock) { + + synchronized (mLock) { mName = source.readString(); - int nativeObject = nativeReadFromParcel(mNativeObject, source); - if (nativeObject !=0 && mNativeObject == 0) { - mCloseGuard.open("release"); - } - mNativeObject = nativeObject; - mGenerationId++; + setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source)); } } @@ -356,7 +353,7 @@ public class Surface implements Parcelable { if (dest == null) { throw new IllegalArgumentException("dest must not be null"); } - synchronized (mNativeObjectLock) { + synchronized (mLock) { dest.writeString(mName); nativeWriteToParcel(mNativeObject, dest); } @@ -367,7 +364,27 @@ public class Surface implements Parcelable { @Override public String toString() { - return "Surface(name=" + mName + ")"; + synchronized (mLock) { + return "Surface(name=" + mName + ")"; + } + } + + private void setNativeObjectLocked(int ptr) { + if (mNativeObject != ptr) { + if (mNativeObject == 0 && ptr != 0) { + mCloseGuard.open("release"); + } else if (mNativeObject != 0 && ptr == 0) { + mCloseGuard.close(); + } + mNativeObject = ptr; + mGenerationId += 1; + } + } + + private void checkNotReleasedLocked() { + if (mNativeObject == 0) { + throw new IllegalStateException("Surface has already been released."); + } } /** @@ -451,9 +468,4 @@ public class Surface implements Parcelable { mOrigMatrix.set(m); } } - - private void checkNotReleasedLocked() { - if (mNativeObject == 0) throw new NullPointerException( - "mNativeObject is null. Have you called release() already?"); - } } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index e869d09..6b530ef 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -496,8 +496,14 @@ public class SurfaceControl { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - int nativeSurface = surface != null ? surface.mNativeObject : 0; - nativeSetDisplaySurface(displayToken, nativeSurface); + + if (surface != null) { + synchronized (surface.mLock) { + nativeSetDisplaySurface(displayToken, surface.mNativeObject); + } + } else { + nativeSetDisplaySurface(displayToken, 0); + } } public static IBinder createDisplay(String name, boolean secure) { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 793fb5e..8b2b556 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -160,7 +160,6 @@ public class SurfaceView extends View { int mHeight = -1; int mFormat = -1; final Rect mSurfaceFrame = new Rect(); - Rect mTmpDirty; int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; boolean mUpdateWindowNeeded; boolean mReportDrawNeeded; @@ -795,14 +794,6 @@ public class SurfaceView extends View { Canvas c = null; if (!mDrawingStopped && mWindow != null) { - if (dirty == null) { - if (mTmpDirty == null) { - mTmpDirty = new Rect(); - } - mTmpDirty.set(mSurfaceFrame); - dirty = mTmpDirty; - } - try { c = mSurface.lockCanvas(dirty); } catch (Exception e) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ed6dc6c..25c380e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11764,10 +11764,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Resolve all RTL related properties. * + * @return true if resolution of RTL properties has been done + * * @hide */ - public void resolveRtlPropertiesIfNeeded() { - if (!needRtlPropertiesResolution()) return; + public boolean resolveRtlPropertiesIfNeeded() { + if (!needRtlPropertiesResolution()) return false; // Order is important here: LayoutDirection MUST be resolved first if (!isLayoutDirectionResolved()) { @@ -11788,6 +11790,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, resolveDrawables(); } onRtlPropertiesChanged(getLayoutDirection()); + return true; } /** @@ -11840,6 +11843,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @return true if RTL properties need resolution. + * */ private boolean needRtlPropertiesResolution() { return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index dfe5f88..f21c63e 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4514,16 +4514,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void offsetChildrenTopAndBottom(int offset) { final int count = mChildrenCount; final View[] children = mChildren; + boolean invalidate = false; for (int i = 0; i < count; i++) { final View v = children[i]; v.mTop += offset; v.mBottom += offset; if (v.mDisplayList != null) { + invalidate = true; v.mDisplayList.offsetTopAndBottom(offset); - invalidateViewProperty(false, false); } } + + if (invalidate) { + invalidateViewProperty(false, false); + } } /** @@ -5525,15 +5530,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public void resolveRtlPropertiesIfNeeded() { - super.resolveRtlPropertiesIfNeeded(); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.isLayoutDirectionInherited()) { - child.resolveRtlPropertiesIfNeeded(); + public boolean resolveRtlPropertiesIfNeeded() { + final boolean result = super.resolveRtlPropertiesIfNeeded(); + // We dont need to resolve the children RTL properties if nothing has changed for the parent + if (result) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resolveRtlPropertiesIfNeeded(); + } } } + return result; } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f34d390..fc005d1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -23,7 +23,6 @@ import android.content.ClipDescription; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -108,8 +107,6 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_FPS = false; private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV; - private static final boolean USE_RENDER_THREAD = false; - /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. @@ -131,10 +128,6 @@ public final class ViewRootImpl implements ViewParent, static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); - private static boolean sUseRenderThread = false; - private static boolean sRenderThreadQueried = false; - private static final Object[] sRenderThreadQueryLock = new Object[0]; - final Context mContext; final IWindowSession mWindowSession; final Display mDisplay; @@ -212,6 +205,7 @@ public final class ViewRootImpl implements ViewParent, boolean mHasHadWindowFocus; boolean mLastWasImTarget; boolean mWindowsAnimating; + boolean mDrawDuringWindowsAnimating; boolean mIsDrawing; int mLastSystemUiVisibility; int mClientWindowLayoutFlags; @@ -375,35 +369,6 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); } - /** - * @return True if the application requests the use of a separate render thread, - * false otherwise - */ - private static boolean isRenderThreadRequested(Context context) { - if (USE_RENDER_THREAD) { - synchronized (sRenderThreadQueryLock) { - if (!sRenderThreadQueried) { - final PackageManager packageManager = context.getPackageManager(); - final String packageName = context.getApplicationInfo().packageName; - try { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, - PackageManager.GET_META_DATA); - if (applicationInfo.metaData != null) { - sUseRenderThread = applicationInfo.metaData.getBoolean( - "android.graphics.renderThread", false); - } - } catch (PackageManager.NameNotFoundException e) { - } finally { - sRenderThreadQueried = true; - } - } - return sUseRenderThread; - } - } else { - return false; - } - } - public static void addFirstDrawHandler(Runnable callback) { synchronized (sFirstDrawHandlers) { if (!sFirstDrawComplete) { @@ -481,7 +446,7 @@ public final class ViewRootImpl implements ViewParent, // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { - enableHardwareAcceleration(mView.getContext(), attrs); + enableHardwareAcceleration(attrs); } boolean restore = false; @@ -689,7 +654,7 @@ public final class ViewRootImpl implements ViewParent, } } - private void enableHardwareAcceleration(Context context, WindowManager.LayoutParams attrs) { + private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; @@ -729,11 +694,6 @@ public final class ViewRootImpl implements ViewParent, return; } - final boolean renderThread = isRenderThreadRequested(context); - if (renderThread) { - Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated"); - } - if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); } @@ -1399,8 +1359,10 @@ public final class ViewRootImpl implements ViewParent, final int surfaceGenerationId = mSurface.getGenerationId(); relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); - mWindowsAnimating |= - (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0; + if (!mDrawDuringWindowsAnimating) { + mWindowsAnimating |= + (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0; + } if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() + " overscan=" + mPendingOverscanInsets.toShortString() @@ -2575,6 +2537,16 @@ public final class ViewRootImpl implements ViewParent, displayLists.clear(); } + /** + * @hide + */ + public void setDrawDuringWindowsAnimating(boolean value) { + mDrawDuringWindowsAnimating = value; + if (value) { + handleDispatchDoneAnimating(); + } + } + boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) { final View.AttachInfo attachInfo = mAttachInfo; final Rect ci = attachInfo.mContentInsets; @@ -2712,7 +2684,6 @@ public final class ViewRootImpl implements ViewParent, AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView; View focusHost = mAccessibilityFocusedHost; - focusHost.clearAccessibilityFocusNoCallbacks(); // Wipe the state of the current accessibility focus since // the call into the provider to clear accessibility focus @@ -2722,6 +2693,10 @@ public final class ViewRootImpl implements ViewParent, mAccessibilityFocusedHost = null; mAccessibilityFocusedVirtualView = null; + // Clear accessibility focus on the host after clearing state since + // this method may be reentrant. + focusHost.clearAccessibilityFocusNoCallbacks(); + AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider(); if (provider != null) { // Invalidate the area of the cleared accessibility focus. diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 4312dee..91c989d 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -16,6 +16,13 @@ package android.widget; +import android.content.UndoManager; +import android.content.UndoOperation; +import android.content.UndoOwner; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.InputFilter; +import android.text.SpannableString; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.EditableInputConnection; @@ -107,11 +114,16 @@ import java.util.HashMap; */ public class Editor { private static final String TAG = "Editor"; + static final boolean DEBUG_UNDO = false; static final int BLINK = 500; private static final float[] TEMP_POSITION = new float[2]; private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; + UndoManager mUndoManager; + UndoOwner mUndoOwner; + InputFilter mUndoInputFilter; + // Cursor Controllers. InsertionPointCursorController mInsertionPointCursorController; SelectionModifierCursorController mSelectionModifierCursorController; @@ -1884,7 +1896,7 @@ public class Editor { /** * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related * pop-up should be displayed. - * Also monitors {@link SelectionSpan} to call back to the attached input method. + * Also monitors {@link Selection} to call back to the attached input method. */ class SpanController implements SpanWatcher { @@ -2014,7 +2026,7 @@ public class Editor { /** * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled - * by {@link EasyEditSpanController}. + * by {@link SpanController}. */ private class EasyEditPopupWindow extends PinnedPopupWindow implements OnClickListener { @@ -3959,4 +3971,166 @@ public class Editor { mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition); } } + + public static class UndoInputFilter implements InputFilter { + final Editor mEditor; + + public UndoInputFilter(Editor editor) { + mEditor = editor; + } + + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + if (DEBUG_UNDO) { + Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ")"); + Log.d(TAG, "filter: dest=" + dest + " (" + dstart + "-" + dend + ")"); + } + final UndoManager um = mEditor.mUndoManager; + if (um.isInUndo()) { + if (DEBUG_UNDO) Log.d(TAG, "*** skipping, currently performing undo/redo"); + return null; + } + + um.beginUpdate("Edit text"); + TextModifyOperation op = um.getLastOperation( + TextModifyOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE); + if (op != null) { + if (DEBUG_UNDO) Log.d(TAG, "Last op: range=(" + op.mRangeStart + "-" + op.mRangeEnd + + "), oldText=" + op.mOldText); + // See if we can continue modifying this operation. + if (op.mOldText == null) { + // The current operation is an add... are we adding more? We are adding + // more if we are either appending new text to the end of the last edit or + // completely replacing some or all of the last edit. + if (start < end && ((dstart >= op.mRangeStart && dend <= op.mRangeEnd) + || (dstart == op.mRangeEnd && dend == op.mRangeEnd))) { + op.mRangeEnd = dstart + (end-start); + um.endUpdate(); + if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, mRangeEnd=" + + op.mRangeEnd); + return null; + } + } else { + // The current operation is a delete... can we delete more? + if (start == end && dend == op.mRangeStart-1) { + SpannableStringBuilder str; + if (op.mOldText instanceof SpannableString) { + str = (SpannableStringBuilder)op.mOldText; + } else { + str = new SpannableStringBuilder(op.mOldText); + } + str.insert(0, dest, dstart, dend); + op.mRangeStart = dstart; + op.mOldText = str; + um.endUpdate(); + if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, range=(" + + op.mRangeStart + "-" + op.mRangeEnd + + "), oldText=" + op.mOldText); + return null; + } + } + + // Couldn't add to the current undo operation, need to start a new + // undo state for a new undo operation. + um.commitState(null); + um.setUndoLabel("Edit text"); + } + + // Create a new undo state reflecting the operation being performed. + op = new TextModifyOperation(mEditor.mUndoOwner); + op.mRangeStart = dstart; + if (start < end) { + op.mRangeEnd = dstart + (end-start); + } else { + op.mRangeEnd = dstart; + } + if (dstart < dend) { + op.mOldText = dest.subSequence(dstart, dend); + } + if (DEBUG_UNDO) Log.d(TAG, "*** adding new op, range=(" + op.mRangeStart + + "-" + op.mRangeEnd + "), oldText=" + op.mOldText); + um.addOperation(op, UndoManager.MERGE_MODE_NONE); + um.endUpdate(); + return null; + } + } + + public static class TextModifyOperation extends UndoOperation<TextView> { + int mRangeStart, mRangeEnd; + CharSequence mOldText; + + public TextModifyOperation(UndoOwner owner) { + super(owner); + } + + public TextModifyOperation(Parcel src, ClassLoader loader) { + super(src, loader); + mRangeStart = src.readInt(); + mRangeEnd = src.readInt(); + mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src); + } + + @Override + public void commit() { + } + + @Override + public void undo() { + swapText(); + } + + @Override + public void redo() { + swapText(); + } + + private void swapText() { + // Both undo and redo involves swapping the contents of the range + // in the text view with our local text. + TextView tv = getOwnerData(); + Editable editable = (Editable)tv.getText(); + CharSequence curText; + if (mRangeStart >= mRangeEnd) { + curText = null; + } else { + curText = editable.subSequence(mRangeStart, mRangeEnd); + } + if (DEBUG_UNDO) { + Log.d(TAG, "Swap: range=(" + mRangeStart + "-" + mRangeEnd + + "), oldText=" + mOldText); + Log.d(TAG, "Swap: curText=" + curText); + } + if (mOldText == null) { + editable.delete(mRangeStart, mRangeEnd); + mRangeEnd = mRangeStart; + } else { + editable.replace(mRangeStart, mRangeEnd, mOldText); + mRangeEnd = mRangeStart + mOldText.length(); + } + mOldText = curText; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRangeStart); + dest.writeInt(mRangeEnd); + TextUtils.writeToParcel(mOldText, dest, flags); + } + + public static final Parcelable.ClassLoaderCreator<TextModifyOperation> CREATOR + = new Parcelable.ClassLoaderCreator<TextModifyOperation>() { + public TextModifyOperation createFromParcel(Parcel in) { + return new TextModifyOperation(in, null); + } + + public TextModifyOperation createFromParcel(Parcel in, ClassLoader loader) { + return new TextModifyOperation(in, loader); + } + + public TextModifyOperation[] newArray(int size) { + return new TextModifyOperation[size]; + } + }; + } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 698f101..8d90973 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -20,6 +20,7 @@ import android.R; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.UndoManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; import android.content.res.Resources; @@ -1510,6 +1511,47 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Retrieve the {@link android.content.UndoManager} that is currently associated + * with this TextView. By default there is no associated UndoManager, so null + * is returned. One can be associated with the TextView through + * {@link #setUndoManager(android.content.UndoManager, String)} + */ + public final UndoManager getUndoManager() { + return mEditor == null ? null : mEditor.mUndoManager; + } + + /** + * Associate an {@link android.content.UndoManager} with this TextView. Once + * done, all edit operations on the TextView will result in appropriate + * {@link android.content.UndoOperation} objects pushed on the given UndoManager's + * stack. + * + * @param undoManager The {@link android.content.UndoManager} to associate with + * this TextView, or null to clear any existing association. + * @param tag String tag identifying this particular TextView owner in the + * UndoManager. This is used to keep the correct association with the + * {@link android.content.UndoOwner} of any operations inside of the UndoManager. + */ + public final void setUndoManager(UndoManager undoManager, String tag) { + if (undoManager != null) { + createEditorIfNeeded(); + mEditor.mUndoManager = undoManager; + mEditor.mUndoOwner = undoManager.getOwner(tag, this); + mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor); + if (!(mText instanceof Editable)) { + setText(mText, BufferType.EDITABLE); + } + + setFilters((Editable) mText, mFilters); + } else if (mEditor != null) { + // XXX need to destroy all associated state. + mEditor.mUndoManager = null; + mEditor.mUndoOwner = null; + mEditor.mUndoInputFilter = null; + } + } + + /** * @return the current key listener for this TextView. * This will frequently be null for non-EditText TextViews. * @@ -4401,16 +4443,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * and includes mInput in the list if it is an InputFilter. */ private void setFilters(Editable e, InputFilter[] filters) { - if (mEditor != null && mEditor.mKeyListener instanceof InputFilter) { - InputFilter[] nf = new InputFilter[filters.length + 1]; - - System.arraycopy(filters, 0, nf, 0, filters.length); - nf[filters.length] = (InputFilter) mEditor.mKeyListener; + if (mEditor != null) { + final boolean undoFilter = mEditor.mUndoInputFilter != null; + final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; + int num = 0; + if (undoFilter) num++; + if (keyFilter) num++; + if (num > 0) { + InputFilter[] nf = new InputFilter[filters.length + num]; + + System.arraycopy(filters, 0, nf, 0, filters.length); + num = 0; + if (undoFilter) { + nf[filters.length] = mEditor.mUndoInputFilter; + num++; + } + if (keyFilter) { + nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; + } - e.setFilters(nf); - } else { - e.setFilters(filters); + e.setFilters(nf); + return; + } } + e.setFilters(filters); } /** |
