summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/ChooseTypeAndAccountActivity.java19
-rw-r--r--core/java/android/animation/ObjectAnimator.java30
-rw-r--r--core/java/android/animation/ValueAnimator.java14
-rw-r--r--core/java/android/app/Activity.java5
-rw-r--r--core/java/android/app/ActivityManager.java184
-rw-r--r--core/java/android/app/ActivityManagerNative.java160
-rw-r--r--core/java/android/app/ActivityThread.java123
-rw-r--r--core/java/android/app/AppOpsManager.java42
-rw-r--r--core/java/android/app/ApplicationThreadNative.java12
-rw-r--r--core/java/android/app/Dialog.java5
-rw-r--r--core/java/android/app/IActivityManager.java41
-rw-r--r--core/java/android/app/IApplicationThread.java4
-rw-r--r--core/java/android/app/LoaderManager.java4
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java8
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java18
-rw-r--r--core/java/android/bluetooth/BluetoothGattServer.java14
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java21
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/IBluetoothHeadset.aidl1
-rw-r--r--core/java/android/content/ContentResolver.java3
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/Intent.java64
-rw-r--r--core/java/android/content/UndoManager.java932
-rw-r--r--core/java/android/content/UndoOperation.java110
-rw-r--r--core/java/android/content/UndoOwner.java55
-rw-r--r--core/java/android/content/pm/ComponentInfo.java11
-rw-r--r--core/java/android/content/pm/KeySet.java34
-rw-r--r--core/java/android/content/pm/PackageParser.java106
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java2
-rw-r--r--core/java/android/content/pm/ResolveInfo.java5
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java4
-rw-r--r--core/java/android/content/res/Resources.java16
-rw-r--r--core/java/android/database/AbstractCursor.java4
-rw-r--r--core/java/android/database/Cursor.java10
-rw-r--r--core/java/android/database/CursorWrapper.java4
-rw-r--r--core/java/android/hardware/Camera.java155
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/os/BatteryStats.java99
-rw-r--r--core/java/android/os/Build.java5
-rw-r--r--core/java/android/os/Debug.java150
-rw-r--r--core/java/android/os/Environment.java5
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/Looper.java10
-rw-r--r--core/java/android/os/MessageQueue.java5
-rw-r--r--core/java/android/os/ParcelableParcel.java75
-rw-r--r--core/java/android/os/PowerManager.java10
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/RecoverySystem.java17
-rw-r--r--core/java/android/os/RemoteCallbackList.java16
-rw-r--r--core/java/android/os/StrictMode.java12
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/provider/DocumentsContract.java209
-rw-r--r--core/java/android/provider/DrmStore.java211
-rw-r--r--core/java/android/provider/OpenableColumns.java14
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/speech/tts/TtsEngines.java13
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java2
-rw-r--r--core/java/android/text/method/BaseKeyListener.java2
-rw-r--r--core/java/android/text/method/BaseMovementMethod.java2
-rw-r--r--core/java/android/text/method/DialerKeyListener.java2
-rw-r--r--core/java/android/text/method/LinkMovementMethod.java5
-rw-r--r--core/java/android/text/method/MetaKeyKeyListener.java82
-rw-r--r--core/java/android/text/method/NumberKeyListener.java2
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java2
-rw-r--r--core/java/android/util/ArrayMap.java617
-rw-r--r--core/java/android/util/LongSparseArray.java16
-rw-r--r--core/java/android/util/LongSparseLongArray.java16
-rw-r--r--core/java/android/util/MapCollections.java496
-rw-r--r--core/java/android/util/SparseArray.java62
-rw-r--r--core/java/android/util/SparseBooleanArray.java46
-rw-r--r--core/java/android/util/SparseIntArray.java45
-rw-r--r--core/java/android/util/SparseLongArray.java45
-rw-r--r--core/java/android/view/GLES20Canvas.java75
-rw-r--r--core/java/android/view/GLES20DisplayList.java9
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java11
-rw-r--r--core/java/android/view/GLES20RenderLayer.java7
-rw-r--r--core/java/android/view/GLES20TextureLayer.java5
-rw-r--r--core/java/android/view/GraphicBuffer.aidl19
-rw-r--r--core/java/android/view/GraphicBuffer.java229
-rw-r--r--core/java/android/view/HardwareLayer.java12
-rw-r--r--core/java/android/view/HardwareRenderer.java208
-rw-r--r--core/java/android/view/IAssetAtlas.aidl54
-rw-r--r--core/java/android/view/IWindowManager.aidl9
-rw-r--r--core/java/android/view/InputEvent.java1
-rw-r--r--core/java/android/view/InputFilter.java6
-rw-r--r--core/java/android/view/Surface.java42
-rw-r--r--core/java/android/view/SurfaceControl.java2
-rw-r--r--core/java/android/view/SurfaceView.java9
-rw-r--r--core/java/android/view/TextureView.java14
-rw-r--r--core/java/android/view/View.java92
-rw-r--r--core/java/android/view/ViewGroup.java88
-rw-r--r--core/java/android/view/ViewRootImpl.java57
-rw-r--r--core/java/android/view/Window.java34
-rw-r--r--core/java/android/view/WindowManager.java8
-rw-r--r--core/java/android/view/WindowManagerPolicy.java15
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java54
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java6
-rw-r--r--core/java/android/view/transition/AutoTransition.java34
-rw-r--r--core/java/android/view/transition/Crossfade.java163
-rw-r--r--core/java/android/view/transition/Fade.java209
-rw-r--r--core/java/android/view/transition/Move.java299
-rw-r--r--core/java/android/view/transition/Recolor.java118
-rw-r--r--core/java/android/view/transition/Rotate.java67
-rw-r--r--core/java/android/view/transition/Scene.java191
-rw-r--r--core/java/android/view/transition/Slide.java70
-rw-r--r--core/java/android/view/transition/TextChange.java90
-rw-r--r--core/java/android/view/transition/Transition.java911
-rw-r--r--core/java/android/view/transition/TransitionGroup.java292
-rw-r--r--core/java/android/view/transition/TransitionInflater.java392
-rw-r--r--core/java/android/view/transition/TransitionManager.java261
-rw-r--r--core/java/android/view/transition/TransitionValues.java60
-rw-r--r--core/java/android/view/transition/Visibility.java243
-rw-r--r--core/java/android/view/transition/package.html25
-rw-r--r--core/java/android/widget/AbsListView.java620
-rw-r--r--core/java/android/widget/Editor.java256
-rw-r--r--core/java/android/widget/GridLayout.java59
-rw-r--r--core/java/android/widget/ListView.java35
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java43
-rw-r--r--core/java/android/widget/ScrollBarDrawable.java6
-rw-r--r--core/java/android/widget/TextClock.java46
-rw-r--r--core/java/android/widget/TextView.java101
-rw-r--r--core/java/android/widget/Toast.java72
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java8
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java51
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java15
-rw-r--r--core/java/com/android/internal/os/BackgroundThread.java54
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java110
-rw-r--r--core/java/com/android/internal/os/ProcessStats.java10
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java13
-rw-r--r--core/java/com/android/internal/policy/IKeyguardExitCallback.aidl20
-rw-r--r--core/java/com/android/internal/policy/IKeyguardService.aidl44
-rw-r--r--core/java/com/android/internal/policy/IKeyguardShowCallback.aidl20
-rw-r--r--core/java/com/android/internal/util/IndentingPrintWriter.java4
-rw-r--r--core/java/com/android/internal/view/RotationPolicy.java21
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java60
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java14
-rw-r--r--core/java/com/android/internal/widget/TransportControlView.java515
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java30
137 files changed, 9446 insertions, 1808 deletions
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 58eb66f..82c2159 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -29,6 +29,7 @@ import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
@@ -127,6 +128,7 @@ public class ChooseTypeAndAccountActivity extends Activity
private int mCallingUid;
private String mCallingPackage;
private boolean mDisallowAddAccounts;
+ private boolean mDontShowPicker;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -189,11 +191,23 @@ public class ChooseTypeAndAccountActivity extends Activity
mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
mAlwaysPromptForAccount = intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false);
mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+
+ // Need to do this once here to request the window feature. Can't do it in onResume
+ mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
+ if (mAccounts.isEmpty()
+ && mDisallowAddAccounts) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.app_not_authorized);
+ mDontShowPicker = true;
+ }
}
@Override
protected void onResume() {
super.onResume();
+
+ if (mDontShowPicker) return;
+
final AccountManager accountManager = AccountManager.get(this);
mAccounts = getAcceptableAccountChoices(accountManager);
@@ -206,11 +220,6 @@ public class ChooseTypeAndAccountActivity extends Activity
// If there are no relevant accounts and only one relevant account type go directly to
// add account. Otherwise let the user choose.
if (mAccounts.isEmpty()) {
- if (mDisallowAddAccounts) {
- setContentView(R.layout.app_not_authorized);
- setTitle(R.string.error_message_title);
- return;
- }
if (mSetOfRelevantAccountTypes.size() == 1) {
runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
} else {
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 173ee73..9c88ccf 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -123,9 +123,37 @@ public final class ObjectAnimator extends ValueAnimator {
* in a call to the function <code>setFoo()</code> on the target object. If either
* <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
* also be derived and called.
+ *
+ * <p>If this animator was created with a {@link Property} object instead of the
+ * string name of a property, then this method will return the {@link
+ * Property#getName() name} of that Property object instead. If this animator was
+ * created with one or more {@link PropertyValuesHolder} objects, then this method
+ * will return the {@link PropertyValuesHolder#getPropertyName() name} of that
+ * object (if there was just one) or a comma-separated list of all of the
+ * names (if there are more than one).</p>
*/
public String getPropertyName() {
- return mPropertyName;
+ String propertyName = null;
+ if (mPropertyName != null) {
+ propertyName = mPropertyName;
+ } else if (mProperty != null) {
+ propertyName = mProperty.getName();
+ } else if (mValues != null && mValues.length > 0) {
+ for (int i = 0; i < mValues.length; ++i) {
+ if (i == 0) {
+ propertyName = "";
+ } else {
+ propertyName += ",";
+ }
+ propertyName += mValues[i].getPropertyName();
+ }
+ }
+ return propertyName;
+ }
+
+ @Override
+ String getNameForTrace() {
+ return "animator:" + getPropertyName();
}
/**
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index cb44264..f8ae616 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -869,7 +869,7 @@ public class ValueAnimator extends Animator {
*
* <p>If this ValueAnimator has only one set of values being animated between, this evaluator
* will be used for that set. If there are several sets of values being animated, which is
- * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator
+ * the case if PropertyValuesHolder objects were set on the ValueAnimator, then the evaluator
* is assigned just to the first PropertyValuesHolder object.</p>
*
* @param value the evaluator to be used this animation
@@ -1024,7 +1024,7 @@ public class ValueAnimator extends Animator {
mStarted = false;
mStartListenersCalled = false;
mPlayingBackwards = false;
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "animator",
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
}
@@ -1033,7 +1033,7 @@ public class ValueAnimator extends Animator {
* called on the UI thread.
*/
private void startAnimation(AnimationHandler handler) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "animator",
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
initAnimation();
handler.mAnimations.add(this);
@@ -1045,6 +1045,14 @@ public class ValueAnimator extends Animator {
}
/**
+ * Returns the name of this animator for debugging purposes.
+ */
+ String getNameForTrace() {
+ return "animator";
+ }
+
+
+ /**
* Internal function called to process an animation frame on an animation that is currently
* sleeping through its <code>startDelay</code> phase. The return value indicates whether it
* should be woken up and put on the active animations queue.
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6b5df7f..7f2f744 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1877,9 +1877,12 @@ public class Activity extends ContextThemeWrapper
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
-
+
mActionBar = new ActionBarImpl(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+
+ mWindow.setDefaultIcon(mActivityInfo.getIconResource());
+ mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index bb9e19f..63ac42e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -31,9 +31,8 @@ import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
-import android.hardware.display.DisplayManager;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -453,14 +452,21 @@ public class ActivityManager {
* Description of the task's last state.
*/
public CharSequence description;
-
+
+ /**
+ * The id of the ActivityStack this Task was on most recently.
+ */
+ public int stackId;
+
public RecentTaskInfo() {
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeInt(persistentId);
@@ -473,6 +479,7 @@ public class ActivityManager {
ComponentName.writeToParcel(origActivity, dest);
TextUtils.writeToParcel(description, dest,
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ dest.writeInt(stackId);
}
public void readFromParcel(Parcel source) {
@@ -485,8 +492,9 @@ public class ActivityManager {
}
origActivity = ComponentName.readFromParcel(source);
description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ stackId = source.readInt();
}
-
+
public static final Creator<RecentTaskInfo> CREATOR
= new Creator<RecentTaskInfo>() {
public RecentTaskInfo createFromParcel(Parcel source) {
@@ -1229,7 +1237,169 @@ public class ActivityManager {
} catch (RemoteException e) {
}
}
-
+
+ /**
+ * Information you can retrieve about the WindowManager StackBox hierarchy.
+ * @hide
+ */
+ public static class StackBoxInfo implements Parcelable {
+ public int stackBoxId;
+ public float weight;
+ public boolean vertical;
+ public Rect bounds;
+ public StackBoxInfo[] children;
+ public int stackId;
+ public StackInfo stack;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(stackBoxId);
+ dest.writeFloat(weight);
+ dest.writeInt(vertical ? 1 : 0);
+ bounds.writeToParcel(dest, flags);
+ dest.writeInt(stackId);
+ if (children != null) {
+ children[0].writeToParcel(dest, flags);
+ children[1].writeToParcel(dest, flags);
+ } else {
+ stack.writeToParcel(dest, flags);
+ }
+ }
+
+ public void readFromParcel(Parcel source) {
+ stackBoxId = source.readInt();
+ weight = source.readFloat();
+ vertical = source.readInt() == 1;
+ bounds = Rect.CREATOR.createFromParcel(source);
+ stackId = source.readInt();
+ if (stackId == -1) {
+ children = new StackBoxInfo[2];
+ children[0] = StackBoxInfo.CREATOR.createFromParcel(source);
+ children[1] = StackBoxInfo.CREATOR.createFromParcel(source);
+ } else {
+ stack = StackInfo.CREATOR.createFromParcel(source);
+ }
+ }
+
+ public static final Creator<StackBoxInfo> CREATOR =
+ new Creator<ActivityManager.StackBoxInfo>() {
+
+ @Override
+ public StackBoxInfo createFromParcel(Parcel source) {
+ return new StackBoxInfo(source);
+ }
+
+ @Override
+ public StackBoxInfo[] newArray(int size) {
+ return new StackBoxInfo[size];
+ }
+ };
+
+ public StackBoxInfo() {
+ }
+
+ public StackBoxInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ public String toString(String prefix) {
+ StringBuilder sb = new StringBuilder(256);
+ sb.append(prefix); sb.append("Box id=" + stackBoxId); sb.append(" weight=" + weight);
+ sb.append(" vertical=" + vertical); sb.append(" bounds=" + bounds.toShortString());
+ sb.append("\n");
+ if (children != null) {
+ sb.append(prefix); sb.append("First child=\n");
+ sb.append(children[0].toString(prefix + " "));
+ sb.append(prefix); sb.append("Second child=\n");
+ sb.append(children[1].toString(prefix + " "));
+ } else {
+ sb.append(prefix); sb.append("Stack=\n");
+ sb.append(stack.toString(prefix + " "));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString("");
+ }
+ }
+
+ /**
+ * Information you can retrieve about an ActivityStack in the system.
+ * @hide
+ */
+ public static class StackInfo implements Parcelable {
+ public int stackId;
+ public Rect bounds;
+ public int[] taskIds;
+ public String[] taskNames;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(stackId);
+ dest.writeInt(bounds.left);
+ dest.writeInt(bounds.top);
+ dest.writeInt(bounds.right);
+ dest.writeInt(bounds.bottom);
+ dest.writeIntArray(taskIds);
+ dest.writeStringArray(taskNames);
+ }
+
+ public void readFromParcel(Parcel source) {
+ stackId = source.readInt();
+ bounds = new Rect(
+ source.readInt(), source.readInt(), source.readInt(), source.readInt());
+ taskIds = source.createIntArray();
+ taskNames = source.createStringArray();
+ }
+
+ public static final Creator<StackInfo> CREATOR = new Creator<StackInfo>() {
+ @Override
+ public StackInfo createFromParcel(Parcel source) {
+ return new StackInfo(source);
+ }
+ @Override
+ public StackInfo[] newArray(int size) {
+ return new StackInfo[size];
+ }
+ };
+
+ public StackInfo() {
+ }
+
+ private StackInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ public String toString(String prefix) {
+ StringBuilder sb = new StringBuilder(256);
+ sb.append(prefix); sb.append("Stack id="); sb.append(stackId);
+ sb.append(" bounds="); sb.append(bounds.toShortString()); sb.append("\n");
+ prefix = prefix + " ";
+ for (int i = 0; i < taskIds.length; ++i) {
+ sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]);
+ sb.append(": "); sb.append(taskNames[i]); sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString("");
+ }
+ }
+
/**
* @hide
*/
@@ -1303,10 +1473,12 @@ public class ActivityManager {
public ProcessErrorStateInfo() {
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(condition);
dest.writeString(processName);
@@ -1317,7 +1489,7 @@ public class ActivityManager {
dest.writeString(longMsg);
dest.writeString(stackTrace);
}
-
+
public void readFromParcel(Parcel source) {
condition = source.readInt();
processName = source.readString();
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d4478bf..27e20b9 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -16,6 +16,7 @@
package android.app;
+import android.app.ActivityManager.StackBoxInfo;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -107,7 +108,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
public ActivityManagerNative() {
attachInterface(this, descriptor);
}
-
+
+ @Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
@@ -197,7 +199,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
- String resultWho = data.readString();
+ String resultWho = data.readString();
int requestCode = data.readInt();
int startFlags = data.readInt();
Configuration config = Configuration.CREATOR.createFromParcel(data);
@@ -223,7 +225,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
}
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
- String resultWho = data.readString();
+ String resultWho = data.readString();
int requestCode = data.readInt();
int flagsMask = data.readInt();
int flagsValues = data.readInt();
@@ -236,7 +238,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeInt(result);
return true;
}
-
+
case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -267,7 +269,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case FINISH_SUB_ACTIVITY_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- String resultWho = data.readString();
+ String resultWho = data.readString();
int requestCode = data.readInt();
finishSubActivity(token, resultWho, requestCode);
reply.writeNoException();
@@ -478,14 +480,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IThumbnailReceiver receiver = receiverBinder != null
? IThumbnailReceiver.Stub.asInterface(receiverBinder)
: null;
- List list = getTasks(maxNum, fl, receiver);
+ List<ActivityManager.RunningTaskInfo> list = getTasks(maxNum, fl, receiver);
reply.writeNoException();
int N = list != null ? list.size() : -1;
reply.writeInt(N);
int i;
for (i=0; i<N; i++) {
- ActivityManager.RunningTaskInfo info =
- (ActivityManager.RunningTaskInfo)list.get(i);
+ ActivityManager.RunningTaskInfo info = list.get(i);
info.writeToParcel(reply, 0);
}
return true;
@@ -535,14 +536,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
int maxNum = data.readInt();
int fl = data.readInt();
- List list = getServices(maxNum, fl);
+ List<ActivityManager.RunningServiceInfo> list = getServices(maxNum, fl);
reply.writeNoException();
int N = list != null ? list.size() : -1;
reply.writeInt(N);
int i;
for (i=0; i<N; i++) {
- ActivityManager.RunningServiceInfo info =
- (ActivityManager.RunningServiceInfo)list.get(i);
+ ActivityManager.RunningServiceInfo info = list.get(i);
info.writeToParcel(reply, 0);
}
return true;
@@ -555,7 +555,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeTypedList(list);
return true;
}
-
+
case GET_RUNNING_APP_PROCESSES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
List<ActivityManager.RunningAppProcessInfo> list = getRunningAppProcesses();
@@ -609,6 +609,53 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case CREATE_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ int relativeStackId = data.readInt();
+ int position = data.readInt();
+ float weight = data.readFloat();
+ int res = createStack(taskId, relativeStackId, position, weight);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+
+ case MOVE_TASK_TO_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ int stackId = data.readInt();
+ boolean toTop = data.readInt() != 0;
+ moveTaskToStack(taskId, stackId, toTop);
+ reply.writeNoException();
+ return true;
+ }
+
+ case RESIZE_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int stackBoxId = data.readInt();
+ float weight = data.readFloat();
+ resizeStackBox(stackBoxId, weight);
+ reply.writeNoException();
+ return true;
+ }
+
+ case GET_STACK_BOXES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ List<StackBoxInfo> list = getStackBoxes();
+ reply.writeNoException();
+ reply.writeTypedList(list);
+ return true;
+ }
+
+ case SET_FOCUSED_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int stackId = data.readInt();
+ setFocusedStack(stackId);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_TASK_FOR_ACTIVITY_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -1033,9 +1080,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeInt(res);
return true;
}
-
+
case CLEAR_APP_DATA_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
+ data.enforceInterface(IActivityManager.descriptor);
String packageName = data.readString();
IPackageDataObserver observer = IPackageDataObserver.Stub.asInterface(
data.readStrongBinder());
@@ -1045,7 +1092,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeInt(res ? 1 : 0);
return true;
}
-
+
case GRANT_URI_PERMISSION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -1057,7 +1104,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
-
+
case REVOKE_URI_PERMISSION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -1068,7 +1115,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
-
+
case SHOW_WAITING_FOR_DEBUGGER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -1257,7 +1304,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
-
+
case FORCE_STOP_PACKAGE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String packageName = data.readString();
@@ -1363,7 +1410,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
String pkg = data.readString();
int appid = data.readInt();
- killApplicationWithAppId(pkg, appid);
+ String reason = data.readString();
+ killApplicationWithAppId(pkg, appid, reason);
reply.writeNoException();
return true;
}
@@ -2565,6 +2613,76 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ @Override
+ public int createStack(int taskId, int relativeStackBoxId, int position, float weight)
+ throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(relativeStackBoxId);
+ data.writeInt(position);
+ data.writeFloat(weight);
+ mRemote.transact(CREATE_STACK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+ @Override
+ public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(stackId);
+ data.writeInt(toTop ? 1 : 0);
+ mRemote.transact(MOVE_TASK_TO_STACK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+ @Override
+ public void resizeStackBox(int stackBoxId, float weight) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(stackBoxId);
+ data.writeFloat(weight);
+ mRemote.transact(RESIZE_STACK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+ @Override
+ public List<StackBoxInfo> getStackBoxes() throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_STACK_BOXES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ ArrayList<StackBoxInfo> list = reply.createTypedArrayList(StackBoxInfo.CREATOR);
+ data.recycle();
+ reply.recycle();
+ return list;
+ }
+ @Override
+ public void setFocusedStack(int stackId) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(stackId);
+ mRemote.transact(SET_FOCUSED_STACK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -3575,12 +3693,14 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
}
- public void killApplicationWithAppId(String pkg, int appid) throws RemoteException {
+ public void killApplicationWithAppId(String pkg, int appid, String reason)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(pkg);
data.writeInt(appid);
+ data.writeString(reason);
mRemote.transact(KILL_APPLICATION_WITH_APPID_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4268fa6..4a41896 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -539,17 +539,17 @@ public final class ActivityThread {
IBinder requestToken;
int requestType;
}
-
+
private native void dumpGraphicsInfo(FileDescriptor fd);
private class ApplicationThread extends ApplicationThreadNative {
- private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s";
+ private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%21s %8d";
private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
// Formatting for checkin service - update version if row format changes
- private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
+ private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 2;
private void updatePendingConfiguration(Configuration config) {
synchronized (mPackages) {
@@ -895,17 +895,18 @@ public final class ActivityThread {
@Override
public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin,
- boolean all, String[] args) {
+ boolean dumpInfo, boolean dumpDalvik, String[] args) {
FileOutputStream fout = new FileOutputStream(fd);
PrintWriter pw = new PrintWriter(fout);
try {
- return dumpMemInfo(pw, checkin, all);
+ return dumpMemInfo(pw, checkin, dumpInfo, dumpDalvik);
} finally {
pw.flush();
}
}
- private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all) {
+ private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean dumpInfo,
+ boolean dumpDalvik) {
long nativeMax = Debug.getNativeHeapSize() / 1024;
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -913,7 +914,7 @@ public final class ActivityThread {
Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memInfo);
- if (!all) {
+ if (!dumpInfo) {
return memInfo;
}
@@ -970,20 +971,42 @@ public final class ActivityThread {
pw.print(memInfo.otherPss); pw.print(',');
pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(',');
- // Heap info - shared
+ // Heap info - proportional set size
+ pw.print(memInfo.nativeSwappablePss); pw.print(',');
+ pw.print(memInfo.dalvikSwappablePss); pw.print(',');
+ pw.print(memInfo.otherSwappablePss); pw.print(',');
+ pw.print(memInfo.nativeSwappablePss + memInfo.dalvikSwappablePss + memInfo.otherSwappablePss); pw.print(',');
+
+ // Heap info - shared dirty
pw.print(memInfo.nativeSharedDirty); pw.print(',');
pw.print(memInfo.dalvikSharedDirty); pw.print(',');
pw.print(memInfo.otherSharedDirty); pw.print(',');
pw.print(memInfo.nativeSharedDirty + memInfo.dalvikSharedDirty
+ memInfo.otherSharedDirty); pw.print(',');
- // Heap info - private
+ // Heap info - shared clean
+ pw.print(memInfo.nativeSharedClean); pw.print(',');
+ pw.print(memInfo.dalvikSharedClean); pw.print(',');
+ pw.print(memInfo.otherSharedClean); pw.print(',');
+ pw.print(memInfo.nativeSharedClean + memInfo.dalvikSharedClean
+ + memInfo.otherSharedClean); pw.print(',');
+
+ // Heap info - private Dirty
pw.print(memInfo.nativePrivateDirty); pw.print(',');
pw.print(memInfo.dalvikPrivateDirty); pw.print(',');
pw.print(memInfo.otherPrivateDirty); pw.print(',');
pw.print(memInfo.nativePrivateDirty + memInfo.dalvikPrivateDirty
+ memInfo.otherPrivateDirty); pw.print(',');
+
+ // Heap info - private Clean
+ pw.print(memInfo.nativePrivateClean); pw.print(',');
+ pw.print(memInfo.dalvikPrivateClean); pw.print(',');
+ pw.print(memInfo.otherPrivateClean); pw.print(',');
+ pw.print(memInfo.nativePrivateClean + memInfo.dalvikPrivateClean
+ + memInfo.otherPrivateClean); pw.print(',');
+
+
// Object counts
pw.print(viewInstanceCount); pw.print(',');
pw.print(viewRootInstanceCount); pw.print(',');
@@ -1018,34 +1041,80 @@ public final class ActivityThread {
}
// otherwise, show human-readable format
- printRow(pw, HEAP_COLUMN, "", "", "Shared", "Private", "Heap", "Heap", "Heap");
- printRow(pw, HEAP_COLUMN, "", "Pss", "Dirty", "Dirty", "Size", "Alloc", "Free");
+ printRow(pw, HEAP_COLUMN, "", "Pss", "Pss","Shared", "Private", "Shared", "Private",
+ "Heap", "Heap", "Heap");
+ printRow(pw, HEAP_COLUMN, "", "Total", "Clean", "Dirty", "Dirty", "Clean", "Clean",
+ "Size", "Alloc", "Free");
printRow(pw, HEAP_COLUMN, "", "------", "------", "------", "------", "------",
- "------");
- printRow(pw, HEAP_COLUMN, "Native", memInfo.nativePss, memInfo.nativeSharedDirty,
- memInfo.nativePrivateDirty, nativeMax, nativeAllocated, nativeFree);
- printRow(pw, HEAP_COLUMN, "Dalvik", memInfo.dalvikPss, memInfo.dalvikSharedDirty,
- memInfo.dalvikPrivateDirty, dalvikMax, dalvikAllocated, dalvikFree);
+ "------", "------", "------", "------");
+ printRow(pw, HEAP_COLUMN, "Native Heap", memInfo.nativePss, memInfo.nativeSwappablePss,
+ memInfo.nativeSharedDirty,
+ memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
+ memInfo.nativePrivateClean, nativeMax, nativeAllocated, nativeFree);
+ printRow(pw, HEAP_COLUMN, "Dalvik Heap", memInfo.dalvikPss, memInfo.dalvikSwappablePss,
+ memInfo.dalvikSharedDirty,
+ memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
+ memInfo.dalvikPrivateClean, dalvikMax, dalvikAllocated, dalvikFree);
int otherPss = memInfo.otherPss;
+ int otherSwappablePss = memInfo.otherSwappablePss;
int otherSharedDirty = memInfo.otherSharedDirty;
int otherPrivateDirty = memInfo.otherPrivateDirty;
+ int otherSharedClean = memInfo.otherSharedClean;
+ int otherPrivateClean = memInfo.otherPrivateClean;
for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
- printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
- memInfo.getOtherPss(i), memInfo.getOtherSharedDirty(i),
- memInfo.getOtherPrivateDirty(i), "", "", "");
- otherPss -= memInfo.getOtherPss(i);
- otherSharedDirty -= memInfo.getOtherSharedDirty(i);
- otherPrivateDirty -= memInfo.getOtherPrivateDirty(i);
+ final int myPss = memInfo.getOtherPss(i);
+ final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+ final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+ final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+ final int mySharedClean = memInfo.getOtherSharedClean(i);
+ final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+ if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+ || mySharedClean != 0 || myPrivateClean != 0) {
+ printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+ myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+ mySharedClean, myPrivateClean, "", "", "");
+ otherPss -= myPss;
+ otherSwappablePss -= mySwappablePss;
+ otherSharedDirty -= mySharedDirty;
+ otherPrivateDirty -= myPrivateDirty;
+ otherSharedClean -= mySharedClean;
+ otherPrivateClean -= myPrivateClean;
+ }
}
- printRow(pw, HEAP_COLUMN, "Unknown", otherPss, otherSharedDirty,
- otherPrivateDirty, "", "", "");
+
+
+ printRow(pw, HEAP_COLUMN, "Unknown", otherPss, otherSwappablePss, otherSharedDirty,
+ otherPrivateDirty, otherSharedClean, otherPrivateClean,"", "", "");
printRow(pw, HEAP_COLUMN, "TOTAL", memInfo.getTotalPss(),
+ memInfo.getTotalSwappablePss(),
memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
- nativeMax+dalvikMax, nativeAllocated+dalvikAllocated,
- nativeFree+dalvikFree);
+ memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
+ nativeMax+dalvikMax,
+ nativeAllocated+dalvikAllocated, nativeFree+dalvikFree);
+
+ if (dumpDalvik) {
+ pw.println(" ");
+ pw.println(" Dalvik Details");
+
+ for (int i=Debug.MemoryInfo.NUM_OTHER_STATS;
+ i<Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS; i++) {
+ final int myPss = memInfo.getOtherPss(i);
+ final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+ final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+ final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+ final int mySharedClean = memInfo.getOtherSharedClean(i);
+ final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+ if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+ || mySharedClean != 0 || myPrivateClean != 0) {
+ printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+ myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+ mySharedClean, myPrivateClean, "", "", "");
+ }
+ }
+ }
pw.println(" ");
pw.println(" Objects");
@@ -1748,7 +1817,7 @@ public final class ActivityThread {
r.getAssets().close();
return existing;
}
-
+
// XXX need to remove entries when weak references go away
mActiveResources.put(key, new WeakReference<Resources>(r));
return r;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index c9776f1..32bb71e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -95,8 +95,18 @@ 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;
+ public static final int OP_WAKE_LOCK = 40;
/** @hide */
- public static final int _NUM_OP = 31;
+ public static final int _NUM_OP = 41;
/**
* This maps each operation to the operation that serves as the
@@ -138,6 +148,16 @@ 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,
+ OP_WAKE_LOCK,
};
/**
@@ -176,6 +196,16 @@ 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",
+ "WAKE_LOCK",
};
/**
@@ -214,6 +244,16 @@ 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
+ android.Manifest.permission.WAKE_LOCK,
};
/**
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index b1c58f2..e903447 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -524,12 +524,13 @@ public abstract class ApplicationThreadNative extends Binder
data.enforceInterface(IApplicationThread.descriptor);
ParcelFileDescriptor fd = data.readFileDescriptor();
boolean checkin = data.readInt() != 0;
- boolean all = data.readInt() != 0;
+ boolean dumpInfo = data.readInt() != 0;
+ boolean dumpDalvik = data.readInt() != 0;
String[] args = data.readStringArray();
Debug.MemoryInfo mi = null;
if (fd != null) {
try {
- mi = dumpMemInfo(fd.getFileDescriptor(), checkin, all, args);
+ mi = dumpMemInfo(fd.getFileDescriptor(), checkin, dumpInfo, dumpDalvik, args);
} finally {
try {
fd.close();
@@ -1159,14 +1160,15 @@ class ApplicationThreadProxy implements IApplicationThread {
IBinder.FLAG_ONEWAY);
}
- public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean all,
- String[] args) throws RemoteException {
+ public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean dumpInfo,
+ boolean dumpDalvik, String[] args) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeFileDescriptor(fd);
data.writeInt(checkin ? 1 : 0);
- data.writeInt(all ? 1 : 0);
+ data.writeInt(dumpInfo ? 1 : 0);
+ data.writeInt(dumpDalvik ? 1 : 0);
data.writeStringArray(args);
mRemote.transact(DUMP_MEM_INFO_TRANSACTION, data, reply, 0);
reply.readException();
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index b3d99c5..634fa30 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,6 +16,8 @@
package android.app;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import com.android.internal.app.ActionBarImpl;
import com.android.internal.policy.PolicyManager;
@@ -264,6 +266,9 @@ public class Dialog implements DialogInterface, Window.Callback,
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
+ final ApplicationInfo info = mContext.getApplicationInfo();
+ mWindow.setDefaultIcon(info.icon);
+ mWindow.setDefaultLogo(info.logo);
mActionBar = new ActionBarImpl(this);
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index a21caee..b48eed2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -16,6 +16,9 @@
package android.app;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.ActivityManager.StackBoxInfo;
import android.content.ComponentName;
import android.content.ContentProviderNative;
import android.content.IContentProvider;
@@ -99,19 +102,25 @@ public interface IActivityManager extends IInterface {
public void activityDestroyed(IBinder token) throws RemoteException;
public String getCallingPackage(IBinder token) throws RemoteException;
public ComponentName getCallingActivity(IBinder token) throws RemoteException;
- public List getTasks(int maxNum, int flags,
+ public List<RunningTaskInfo> getTasks(int maxNum, int flags,
IThumbnailReceiver receiver) throws RemoteException;
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
int flags, int userId) throws RemoteException;
public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException;
public Bitmap getTaskTopThumbnail(int taskId) throws RemoteException;
- public List getServices(int maxNum, int flags) throws RemoteException;
+ public List<RunningServiceInfo> getServices(int maxNum, int flags) throws RemoteException;
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
public void moveTaskToBack(int task) throws RemoteException;
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
public void moveTaskBackwards(int task) throws RemoteException;
+ public int createStack(int taskId, int relativeStackBoxId, int position, float weight)
+ throws RemoteException;
+ public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
+ public void resizeStackBox(int stackBoxId, float weight) throws RemoteException;
+ public List<StackBoxInfo> getStackBoxes() throws RemoteException;
+ public void setFocusedStack(int stackId) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
/* oneway */
public void reportThumbnail(IBinder token,
@@ -149,14 +158,14 @@ public interface IActivityManager extends IInterface {
public void serviceDoneExecuting(IBinder token, int type, int startId,
int res) throws RemoteException;
public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
-
+
public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode)
throws RemoteException;
public void clearPendingBackup() throws RemoteException;
public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;
public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException;
public void killApplicationProcess(String processName, int uid) throws RemoteException;
-
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
IUiAutomationConnection connection, int userId) throws RemoteException;
@@ -168,7 +177,7 @@ public interface IActivityManager extends IInterface {
public void setRequestedOrientation(IBinder token,
int requestedOrientation) throws RemoteException;
public int getRequestedOrientation(IBinder token) throws RemoteException;
-
+
public ComponentName getActivityClassForToken(IBinder token) throws RemoteException;
public String getPackageForToken(IBinder token) throws RemoteException;
@@ -181,16 +190,16 @@ public interface IActivityManager extends IInterface {
final IPackageDataObserver observer, int userId) throws RemoteException;
public String getPackageForIntentSender(IIntentSender sender) throws RemoteException;
public int getUidForIntentSender(IIntentSender sender) throws RemoteException;
-
+
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) throws RemoteException;
public void setProcessLimit(int max) throws RemoteException;
public int getProcessLimit() throws RemoteException;
-
+
public void setProcessForeground(IBinder token, int pid,
boolean isForeground) throws RemoteException;
-
+
public int checkPermission(String permission, int pid, int uid)
throws RemoteException;
@@ -274,7 +283,8 @@ public interface IActivityManager extends IInterface {
public void stopAppSwitches() throws RemoteException;
public void resumeAppSwitches() throws RemoteException;
- public void killApplicationWithAppId(String pkg, int appid) throws RemoteException;
+ public void killApplicationWithAppId(String pkg, int appid, String reason)
+ throws RemoteException;
public void closeSystemDialogs(String reason) throws RemoteException;
@@ -395,10 +405,12 @@ public interface IActivityManager extends IInterface {
info = _info;
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
info.writeToParcel(dest, 0);
if (provider != null) {
@@ -412,10 +424,12 @@ public interface IActivityManager extends IInterface {
public static final Parcelable.Creator<ContentProviderHolder> CREATOR
= new Parcelable.Creator<ContentProviderHolder>() {
+ @Override
public ContentProviderHolder createFromParcel(Parcel source) {
return new ContentProviderHolder(source);
}
+ @Override
public ContentProviderHolder[] newArray(int size) {
return new ContentProviderHolder[size];
}
@@ -441,10 +455,12 @@ public interface IActivityManager extends IInterface {
public WaitResult() {
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(result);
dest.writeInt(timeout ? 1 : 0);
@@ -455,10 +471,12 @@ public interface IActivityManager extends IInterface {
public static final Parcelable.Creator<WaitResult> CREATOR
= new Parcelable.Creator<WaitResult>() {
+ @Override
public WaitResult createFromParcel(Parcel source) {
return new WaitResult(source);
}
+ @Override
public WaitResult[] newArray(int size) {
return new WaitResult[size];
}
@@ -641,4 +659,9 @@ public interface IActivityManager extends IInterface {
int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164;
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_STACK_BOXES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170;
+ int SET_FOCUSED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+171;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 3189b31..a009bd3 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -126,8 +126,8 @@ public interface IApplicationThread extends IInterface {
void setCoreSettings(Bundle coreSettings) throws RemoteException;
void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) throws RemoteException;
void scheduleTrimMemory(int level) throws RemoteException;
- Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean all,
- String[] args) throws RemoteException;
+ Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean dumpInfo,
+ boolean dumpDalvik, String[] args) throws RemoteException;
void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException;
void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException;
void unstableProviderDied(IBinder provider) throws RemoteException;
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 267555a..b13b24a 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -204,13 +204,13 @@ class LoaderManagerImpl extends LoaderManager {
// These are the currently active loaders. A loader is here
// from the time its load is started until it has been explicitly
// stopped or restarted by the application.
- final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
+ final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
// These are previously run loaders. This list is maintained internally
// to avoid destroying a loader while an application is still using it.
// It allows an application to restart a loader, but continue using its
// previously run loader until the new loader's data is available.
- final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
+ final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);
final String mWho;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index c62bf32..3a355f9 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -198,7 +198,6 @@ public class AppWidgetHost {
* @return a appWidgetId
*/
public int allocateAppWidgetId() {
-
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
@@ -211,20 +210,17 @@ public class AppWidgetHost {
}
/**
- * Get a appWidgetId for a host in the calling process.
+ * Get a appWidgetId for a host in the given package.
*
* @return a appWidgetId
* @hide
*/
- public static int allocateAppWidgetIdForSystem(int hostId, int userId) {
+ public static int allocateAppWidgetIdForPackage(int hostId, int userId, String packageName) {
checkCallerIsSystem();
try {
if (sService == null) {
bindService();
}
- Context systemContext =
- (Context) ActivityThread.currentActivityThread().getSystemContext();
- String packageName = systemContext.getPackageName();
return sService.allocateAppWidgetId(packageName, hostId, userId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 0752df8..df3ec1a 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -167,7 +167,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
synchronized(mStateLock) {
@@ -294,7 +294,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onServicesDiscovered(BluetoothGatt.this, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -341,7 +341,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -387,7 +387,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -418,7 +418,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -467,7 +467,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -515,7 +515,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -532,7 +532,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -549,7 +549,7 @@ public final class BluetoothGatt implements BluetoothProfile {
try {
mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
};
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 78d536b..58ee54f 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -108,7 +108,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
connected ? BluetoothProfile.STATE_CONNECTED :
BluetoothProfile.STATE_DISCONNECTED);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -128,7 +128,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
try {
mCallback.onServiceAdded((int)status, service);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -154,7 +154,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
try {
mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -186,7 +186,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
try {
mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -214,7 +214,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
isPrep, needRsp, offset, value);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -250,7 +250,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
mCallback.onDescriptorWriteRequest(device, transId, descriptor,
isPrep, needRsp, offset, value);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
@@ -270,7 +270,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
try {
mCallback.onExecuteWrite(device, transId, execWrite);
} catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
+ Log.w(TAG, "Unhandled exception in callback", ex);
}
}
};
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 793d798..963e9fc 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -815,27 +815,6 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
- * Notify Headset of phone roam state change.
- * This is a backdoor for phone app to call BluetoothHeadset since
- * there is currently not a good way to get roaming state change outside
- * of phone app.
- *
- * @hide
- */
- public void roamChanged(boolean roaming) {
- if (mService != null && isEnabled()) {
- try {
- mService.roamChanged(roaming);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- }
-
- /**
* Send Headset of CLCC response
*
* @hide
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index fc7627a..285eea7 100644..100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -50,7 +50,6 @@ interface IBluetoothHeadset {
boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
- void roamChanged(boolean roam);
void clccResponse(int index, int direction, int status, int mode, boolean mpty,
String number, int type);
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index fefd343..b3f0d96 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -220,6 +220,7 @@ public abstract class ContentResolver {
// Always log queries which take 500ms+; shorter queries are
// sampled accordingly.
+ private static final boolean ENABLE_CONTENT_SAMPLE = false;
private static final int SLOW_THRESHOLD_MILLIS = 500;
private final Random mRandom = new Random(); // guarded by itself
@@ -1832,6 +1833,7 @@ public abstract class ContentResolver {
private void maybeLogQueryToEventLog(long durationMillis,
Uri uri, String[] projection,
String selection, String sortOrder) {
+ if (!ENABLE_CONTENT_SAMPLE) return;
int samplePercent = samplePercentForDuration(durationMillis);
if (samplePercent < 100) {
synchronized (mRandom) {
@@ -1871,6 +1873,7 @@ public abstract class ContentResolver {
private void maybeLogUpdateToEventLog(
long durationMillis, Uri uri, String operation, String selection) {
+ if (!ENABLE_CONTENT_SAMPLE) return;
int samplePercent = samplePercentForDuration(durationMillis);
if (samplePercent < 100) {
synchronized (mRandom) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5bd28b9..81d6f0b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2434,7 +2434,7 @@ public abstract class Context {
* Remove all permissions to access a particular content provider Uri
* that were previously added with {@link #grantUriPermission}. The given
* Uri will match all previously granted Uris that are the same or a
- * sub-path of the given Uri. That is, revoking "content://foo/one" will
+ * sub-path of the given Uri. That is, revoking "content://foo/target" will
* revoke both "content://foo/target" and "content://foo/target/sub", but not
* "content://foo".
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5cb856e..e25af70 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1165,13 +1165,12 @@ public class Intent implements Parcelable, Cloneable {
* additional optional contextual information about where the user was when they requested
* the voice assist.
* Output: nothing.
- * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
/**
- * An optional field on {@link #ACTION_ASSIST}
+ * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
* containing the name of the current foreground application package at the time
* the assist was invoked.
*/
@@ -1179,7 +1178,7 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.extra.ASSIST_PACKAGE";
/**
- * An optional field on {@link #ACTION_ASSIST}
+ * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
* containing additional contextual information supplied by the current
* foreground app at the time of the assist request. This is a {@link Bundle} of
* additional data.
@@ -2597,6 +2596,46 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
+ /**
+ * Activity Action: Allow the user to select and open one or more existing
+ * documents. Both read and write access to the documents will be granted
+ * until explicitly revoked by the user.
+ * <p>
+ * Callers can restrict selection to a specific kind of data, such as
+ * photos, by setting one or more MIME types in {@link #EXTRA_MIME_TYPES}.
+ * <p>
+ * If the caller can handle multiple returned items (the user performing
+ * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE} to
+ * indicate this.
+ * <p>
+ * All returned URIs can be opened as a stream with
+ * {@link ContentResolver#openInputStream(Uri)}.
+ * <p>
+ * Output: The URI of the item that was picked. This must be a content: URI
+ * so that any receiver can access it.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
+
+ /**
+ * Activity Action: Allow the user to create a new document. Both read and
+ * write access to the document will be granted until explicitly revoked by
+ * the user.
+ * <p>
+ * Callers can provide a hint document name by setting {@link #EXTRA_TITLE},
+ * but the user may change this value before creating the file. Callers can
+ * optionally hint at the MIME type being created by setting
+ * {@link #setType(String)}.
+ * <p>
+ * All returned URIs can be opened as a stream with
+ * {@link ContentResolver#openOutputStream(Uri)}.
+ * <p>
+ * Output: The URI of the item that was created. This must be a content: URI
+ * so that any receiver can access it.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -3200,6 +3239,13 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_RESTRICTIONS_INTENT =
"android.intent.extra.restrictions_intent";
+ /**
+ * Extra used to communicate set of acceptable MIME types for
+ * {@link #ACTION_GET_CONTENT} or {@link #ACTION_OPEN_DOCUMENT}. The type of the
+ * extra is <code>ArrayList&lt;String&gt;</code>.
+ */
+ public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
@@ -3249,6 +3295,15 @@ public class Intent implements Parcelable, Cloneable {
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020;
/**
+ * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+ * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the grant will be remembered
+ * until explicitly revoked with
+ * {@link Context#revokeUriPermission(Uri, int)}. These grants persist
+ * across device reboots.
+ */
+ public static final int FLAG_PERSIST_GRANT_URI_PERMISSION = 0x00000040;
+
+ /**
* If set, the new activity is not kept in the history stack. As soon as
* the user navigates away from it, the activity is finished. This may also
* be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
@@ -7037,7 +7092,8 @@ public class Intent implements Parcelable, Cloneable {
// and flags to ourselves to grant.
setClipData(target.getClipData());
addFlags(target.getFlags()
- & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION));
+ & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION
+ | FLAG_PERSIST_GRANT_URI_PERMISSION));
return true;
} else {
return false;
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/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 2812477..4dbcf23 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -116,6 +116,17 @@ public class ComponentInfo extends PackageItemInfo {
public final int getIconResource() {
return icon != 0 ? icon : applicationInfo.icon;
}
+
+ /**
+ * Return the logo resource identifier to use for this component. If
+ * the component defines a logo, that is used; else, the application
+ * logo is used.
+ *
+ * @return The logo associated with this component.
+ */
+ public final int getLogoResource() {
+ return logo != 0 ? logo : applicationInfo.logo;
+ }
protected void dumpFront(Printer pw, String prefix) {
super.dumpFront(pw, prefix);
diff --git a/core/java/android/content/pm/KeySet.java b/core/java/android/content/pm/KeySet.java
new file mode 100644
index 0000000..0ef09a4
--- /dev/null
+++ b/core/java/android/content/pm/KeySet.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Binder;
+
+/** @hide */
+public class KeySet {
+
+ private Binder token;
+
+ /** @hide */
+ public KeySet(Binder token) {
+ this.token = token;
+ }
+
+ Binder getToken() {
+ return token;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 34e0c12..1b997f0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -45,14 +45,20 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
@@ -714,6 +720,13 @@ public class PackageParser {
mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
return false;
}
+
+ // Add the signing KeySet to the system
+ pkg.mSigningKeys = new HashSet<PublicKey>();
+ for (int i=0; i < certs.length; i++) {
+ pkg.mSigningKeys.add(certs[i].getPublicKey());
+ }
+
} catch (CertificateEncodingException e) {
Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
@@ -1027,6 +1040,10 @@ public class PackageParser {
if (!parseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
+ } else if (tagName.equals("keys")) {
+ if (!parseKeys(pkg, res, parser, attrs, outError)) {
+ return null;
+ }
} else if (tagName.equals("permission-group")) {
if (parsePermissionGroup(pkg, flags, res, parser, attrs, outError) == null) {
return null;
@@ -1519,7 +1536,71 @@ public class PackageParser {
}
return buildCompoundName(pkg, procSeq, "taskAffinity", outError);
}
-
+
+ private boolean parseKeys(Package owner, Resources res,
+ XmlPullParser parser, AttributeSet attrs, String[] outError)
+ throws XmlPullParserException, IOException {
+ // we've encountered the 'keys' tag
+ // all the keys and keysets that we want must be defined here
+ // so we're going to iterate over the parser and pull out the things we want
+ int outerDepth = parser.getDepth();
+
+ int type;
+ PublicKey currentKey = null;
+ Map<PublicKey, Set<String>> definedKeySets = new HashMap<PublicKey, Set<String>>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG) {
+ continue;
+ }
+ String tagname = parser.getName();
+ if (tagname.equals("publicKey")) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.PublicKey);
+ final String encodedKey = sa.getNonResourceString(
+ com.android.internal.R.styleable.PublicKey_value);
+ currentKey = parsePublicKey(encodedKey);
+ definedKeySets.put(currentKey, new HashSet<String>());
+ sa.recycle();
+ } else if (tagname.equals("keyset")) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.KeySet);
+ final String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.KeySet_name);
+ definedKeySets.get(currentKey).add(name);
+ sa.recycle();
+ } else if (RIGID_PARSER) {
+ Slog.w(TAG, "Bad element under <keys>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ return false;
+ } else {
+ Slog.w(TAG, "Unknown element under <keys>: " + parser.getName()
+ + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+
+ owner.mKeySetMapping = new HashMap<String, Set<PublicKey>>();
+ for (Map.Entry<PublicKey, Set<String>> e : definedKeySets.entrySet()) {
+ PublicKey key = e.getKey();
+ Set<String> keySetNames = e.getValue();
+ for (String alias : keySetNames) {
+ if (owner.mKeySetMapping.containsKey(alias)) {
+ owner.mKeySetMapping.get(alias).add(key);
+ } else {
+ Set<PublicKey> keys = new HashSet<PublicKey>();
+ keys.add(key);
+ owner.mKeySetMapping.put(alias, keys);
+ }
+ }
+ }
+
+ return true;
+ }
+
private PermissionGroup parsePermissionGroup(Package owner, int flags, Resources res,
XmlPullParser parser, AttributeSet attrs, String[] outError)
throws XmlPullParserException, IOException {
@@ -3083,20 +3164,28 @@ public class PackageParser {
Slog.i(TAG, "verifier " + packageName + " public key was null; skipping");
}
+ PublicKey publicKey = parsePublicKey(encodedPublicKey);
+ if (publicKey != null) {
+ return new VerifierInfo(packageName, publicKey);
+ }
+
+ return null;
+ }
+
+ public static final PublicKey parsePublicKey(String encodedPublicKey) {
EncodedKeySpec keySpec;
try {
final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT);
keySpec = new X509EncodedKeySpec(encoded);
} catch (IllegalArgumentException e) {
- Slog.i(TAG, "Could not parse verifier " + packageName + " public key; invalid Base64");
+ Slog.i(TAG, "Could not parse verifier public key; invalid Base64");
return null;
}
/* First try the key as an RSA key. */
try {
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- final PublicKey publicKey = keyFactory.generatePublic(keySpec);
- return new VerifierInfo(packageName, publicKey);
+ return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Could not parse public key because RSA isn't included in build");
return null;
@@ -3107,8 +3196,7 @@ public class PackageParser {
/* Now try it as a DSA key. */
try {
final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
- final PublicKey publicKey = keyFactory.generatePublic(keySpec);
- return new VerifierInfo(packageName, publicKey);
+ return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Could not parse public key because DSA isn't included in build");
return null;
@@ -3360,6 +3448,12 @@ public class PackageParser {
*/
public ManifestDigest manifestDigest;
+ /**
+ * Data used to feed the KeySetManager
+ */
+ public Set<PublicKey> mSigningKeys;
+ public Map<String, Set<PublicKey>> mKeySetMapping;
+
public Package(String _name) {
packageName = _name;
applicationInfo.packageName = _name;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 288d55f..875e8de 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -82,7 +82,7 @@ public abstract class RegisteredServicesCache<V> {
@GuardedBy("mServicesLock")
private boolean mPersistentServicesFileDidNotExist;
@GuardedBy("mServicesLock")
- private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>();
+ private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
private static class UserServices<V> {
@GuardedBy("mServicesLock")
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 07117fe..de8e256 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -328,6 +328,7 @@ public class ResolveInfo implements Parcelable {
implements Comparator<ResolveInfo> {
public DisplayNameComparator(PackageManager pm) {
mPM = pm;
+ mCollator.setStrength(Collator.PRIMARY);
}
public final int compare(ResolveInfo a, ResolveInfo b) {
@@ -336,10 +337,10 @@ public class ResolveInfo implements Parcelable {
CharSequence sb = b.loadLabel(mPM);
if (sb == null) sb = b.activityInfo.name;
- return sCollator.compare(sa.toString(), sb.toString());
+ return mCollator.compare(sa.toString(), sb.toString());
}
- private final Collator sCollator = Collator.getInstance();
+ private final Collator mCollator = Collator.getInstance();
private PackageManager mPM;
}
}
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 7d46710..e4cc77f 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -30,7 +31,7 @@ import java.io.IOException;
* opened FileDescriptor that can be used to read the data, as well as the
* offset and length of that entry's data in the file.
*/
-public class AssetFileDescriptor implements Parcelable {
+public class AssetFileDescriptor implements Parcelable, Closeable {
/**
* Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
* and {@link #getDeclaredLength} when a length has not been declared. This means
@@ -122,6 +123,7 @@ public class AssetFileDescriptor implements Parcelable {
/**
* Convenience for calling <code>getParcelFileDescriptor().close()</code>.
*/
+ @Override
public void close() throws IOException {
mFd.close();
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 42f4faf..cff974d 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;
@@ -100,11 +99,11 @@ public class Resources {
/*package*/ final Configuration mTmpConfig = new Configuration();
/*package*/ TypedValue mTmpValue = new TypedValue();
/*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
- = new LongSparseArray<WeakReference<Drawable.ConstantState> >();
+ = new LongSparseArray<WeakReference<Drawable.ConstantState> >(0);
/*package*/ final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
- = new LongSparseArray<WeakReference<ColorStateList> >();
+ = new LongSparseArray<WeakReference<ColorStateList> >(0);
/*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
- = new LongSparseArray<WeakReference<Drawable.ConstantState> >();
+ = new LongSparseArray<WeakReference<Drawable.ConstantState> >(0);
/*package*/ boolean mPreloading;
/*package*/ TypedArray mCachedStyledAttributes = null;
@@ -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/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 300b4d1..b5b89dd 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -369,7 +369,9 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
public Uri getNotificationUri() {
- return mNotifyUri;
+ synchronized (mSelfObserverLock) {
+ return mNotifyUri;
+ }
}
public boolean getWantsAllOnMoveCalls() {
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 907833d..7381e2c 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -425,6 +425,16 @@ public interface Cursor extends Closeable {
void setNotificationUri(ContentResolver cr, Uri uri);
/**
+ * Return the URI at which notifications of changes in this Cursor's data
+ * will be delivered, as previously set by {@link #setNotificationUri}.
+ * @return Returns a URI that can be used with
+ * {@link ContentResolver#registerContentObserver(android.net.Uri, boolean, ContentObserver)
+ * ContentResolver.registerContentObserver} to find out about changes to this Cursor's
+ * data. May be null if no notification URI has been set.
+ */
+ Uri getNotificationUri();
+
+ /**
* onMove() will only be called across processes if this method returns true.
* @return whether all cursor movement should result in a call to onMove().
*/
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 7baeb8c..d8fcb17 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -194,6 +194,10 @@ public class CursorWrapper implements Cursor {
mCursor.setNotificationUri(cr, uri);
}
+ public Uri getNotificationUri() {
+ return mCursor.getNotificationUri();
+ }
+
public void unregisterContentObserver(ContentObserver observer) {
mCursor.unregisterContentObserver(observer);
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 4e51080..ac42b76 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -31,6 +31,11 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.RSIllegalArgumentException;
+import android.renderscript.Type;
import android.util.Log;
import android.text.TextUtils;
import android.view.Surface;
@@ -152,6 +157,7 @@ public class Camera {
private PictureCallback mRawImageCallback;
private PictureCallback mJpegCallback;
private PreviewCallback mPreviewCallback;
+ private boolean mUsingPreviewAllocation;
private PictureCallback mPostviewCallback;
private AutoFocusCallback mAutoFocusCallback;
private AutoFocusMoveCallback mAutoFocusMoveCallback;
@@ -327,6 +333,7 @@ public class Camera {
mJpegCallback = null;
mPreviewCallback = null;
mPostviewCallback = null;
+ mUsingPreviewAllocation = false;
mZoomListener = null;
Looper looper;
@@ -587,6 +594,9 @@ public class Camera {
mPreviewCallback = cb;
mOneShot = false;
mWithBuffer = false;
+ if (cb != null) {
+ mUsingPreviewAllocation = false;
+ }
// Always use one-shot mode. We fake camera preview mode by
// doing one-shot preview continuously.
setHasPreviewCallback(cb != null, false);
@@ -610,6 +620,9 @@ public class Camera {
mPreviewCallback = cb;
mOneShot = true;
mWithBuffer = false;
+ if (cb != null) {
+ mUsingPreviewAllocation = false;
+ }
setHasPreviewCallback(cb != null, false);
}
@@ -645,6 +658,9 @@ public class Camera {
mPreviewCallback = cb;
mOneShot = false;
mWithBuffer = true;
+ if (cb != null) {
+ mUsingPreviewAllocation = false;
+ }
setHasPreviewCallback(cb != null, true);
}
@@ -744,6 +760,134 @@ public class Camera {
private native final void _addCallbackBuffer(
byte[] callbackBuffer, int msgType);
+ /**
+ * <p>Create a {@link android.renderscript RenderScript}
+ * {@link android.renderscript.Allocation Allocation} to use as a
+ * destination of preview callback frames. Use
+ * {@link #setPreviewCallbackAllocation setPreviewCallbackAllocation} to use
+ * the created Allocation as a destination for camera preview frames.</p>
+ *
+ * <p>The Allocation will be created with a YUV type, and its contents must
+ * be accessed within Renderscript with the {@code rsGetElementAtYuv_*}
+ * accessor methods. Its size will be based on the current
+ * {@link Parameters#getPreviewSize preview size} configured for this
+ * camera.</p>
+ *
+ * @param rs the RenderScript context for this Allocation.
+ * @param usage additional usage flags to set for the Allocation. The usage
+ * flag {@link android.renderscript.Allocation#USAGE_IO_INPUT} will always
+ * be set on the created Allocation, but additional flags may be provided
+ * here.
+ * @return a new YUV-type Allocation with dimensions equal to the current
+ * preview size.
+ * @throws RSIllegalArgumentException if the usage flags are not compatible
+ * with an YUV Allocation.
+ * @see #setPreviewCallbackAllocation
+ * @hide
+ */
+ public final Allocation createPreviewAllocation(RenderScript rs, int usage)
+ throws RSIllegalArgumentException {
+ Parameters p = getParameters();
+ Size previewSize = p.getPreviewSize();
+ Type.Builder yuvBuilder = new Type.Builder(rs,
+ Element.createPixel(rs,
+ Element.DataType.UNSIGNED_8,
+ Element.DataKind.PIXEL_YUV));
+ // Use YV12 for wide compatibility. Changing this requires also
+ // adjusting camera service's format selection.
+ yuvBuilder.setYuvFormat(ImageFormat.YV12);
+ yuvBuilder.setX(previewSize.width);
+ yuvBuilder.setY(previewSize.height);
+
+ Allocation a = Allocation.createTyped(rs, yuvBuilder.create(),
+ usage | Allocation.USAGE_IO_INPUT);
+
+ return a;
+ }
+
+ /**
+ * <p>Set an {@link android.renderscript.Allocation Allocation} as the
+ * target of preview callback data. Use this method for efficient processing
+ * of camera preview data with RenderScript. The Allocation must be created
+ * with the {@link #createPreviewAllocation createPreviewAllocation }
+ * method.</p>
+ *
+ * <p>Setting a preview allocation will disable any active preview callbacks
+ * set by {@link #setPreviewCallback setPreviewCallback} or
+ * {@link #setPreviewCallbackWithBuffer setPreviewCallbackWithBuffer}, and
+ * vice versa. Using a preview allocation still requires an active standard
+ * preview target to be set, either with
+ * {@link #setPreviewTexture setPreviewTexture} or
+ * {@link #setPreviewDisplay setPreviewDisplay}.</p>
+ *
+ * <p>To be notified when new frames are available to the Allocation, use
+ * {@link android.renderscript.Allocation#setIoInputNotificationHandler Allocation.setIoInputNotificationHandler}. To
+ * update the frame currently accessible from the Allocation to the latest
+ * preview frame, call
+ * {@link android.renderscript.Allocation#ioReceive Allocation.ioReceive}.</p>
+ *
+ * <p>To disable preview into the Allocation, call this method with a
+ * {@code null} parameter.</p>
+ *
+ * <p>Once a preview allocation is set, the preview size set by
+ * {@link Parameters#setPreviewSize setPreviewSize} cannot be changed. If
+ * you wish to change the preview size, first remove the preview allocation
+ * by calling {@code setPreviewCallbackAllocation(null)}, then change the
+ * preview size, create a new preview Allocation with
+ * {@link #createPreviewAllocation createPreviewAllocation}, and set it as
+ * the new preview callback allocation target.</p>
+ *
+ * <p>If you are using the preview data to create video or still images,
+ * strongly consider using {@link android.media.MediaActionSound} to
+ * properly indicate image capture or recording start/stop to the user.</p>
+ *
+ * @param previewAllocation the allocation to use as destination for preview
+ * @throws IOException if configuring the camera to use the Allocation for
+ * preview fails.
+ * @throws IllegalArgumentException if the Allocation's dimensions or other
+ * parameters don't meet the requirements.
+ * @see #createPreviewAllocation
+ * @see #setPreviewCallback
+ * @see #setPreviewCallbackWithBuffer
+ * @hide
+ */
+ public final void setPreviewCallbackAllocation(Allocation previewAllocation)
+ throws IOException {
+ Surface previewSurface = null;
+ if (previewAllocation != null) {
+ Parameters p = getParameters();
+ Size previewSize = p.getPreviewSize();
+ if (previewSize.width != previewAllocation.getType().getX() ||
+ previewSize.height != previewAllocation.getType().getY()) {
+ throw new IllegalArgumentException(
+ "Allocation dimensions don't match preview dimensions: " +
+ "Allocation is " +
+ previewAllocation.getType().getX() +
+ ", " +
+ previewAllocation.getType().getY() +
+ ". Preview is " + previewSize.width + ", " +
+ previewSize.height);
+ }
+ if ((previewAllocation.getUsage() &
+ Allocation.USAGE_IO_INPUT) == 0) {
+ throw new IllegalArgumentException(
+ "Allocation usage does not include USAGE_IO_INPUT");
+ }
+ if (previewAllocation.getType().getElement().getDataKind() !=
+ Element.DataKind.PIXEL_YUV) {
+ throw new IllegalArgumentException(
+ "Allocation is not of a YUV type");
+ }
+ previewSurface = previewAllocation.getSurface();
+ mUsingPreviewAllocation = true;
+ } else {
+ mUsingPreviewAllocation = false;
+ }
+ setPreviewCallbackSurface(previewSurface);
+ }
+
+ private native final void setPreviewCallbackSurface(Surface s);
+
private class EventHandler extends Handler
{
private Camera mCamera;
@@ -1492,6 +1636,17 @@ public class Camera {
* @see #getParameters()
*/
public void setParameters(Parameters params) {
+ // If using preview allocations, don't allow preview size changes
+ if (mUsingPreviewAllocation) {
+ Size newPreviewSize = params.getPreviewSize();
+ Size currentPreviewSize = getParameters().getPreviewSize();
+ if (newPreviewSize.width != currentPreviewSize.width ||
+ newPreviewSize.height != currentPreviewSize.height) {
+ throw new IllegalStateException("Cannot change preview size" +
+ " while a preview allocation is configured.");
+ }
+ }
+
native_setParameters(params.flatten());
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4881d14..7f82ce3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -381,7 +381,6 @@ public class InputMethodService extends AbstractInputMethodService {
if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
+ " ic=" + mInputConnection);
onUnbindInput();
- mInputStarted = false;
mInputBinding = null;
mInputConnection = null;
}
@@ -719,7 +718,7 @@ public class InputMethodService extends AbstractInputMethodService {
super.onDestroy();
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
mInsetsComputer);
- finishViews();
+ doFinishInput();
if (mWindowAdded) {
// Disable exit animation for the current IME window
// to avoid the race condition between the exit and enter animations
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 499ec77..d0f7511 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -98,6 +98,11 @@ public abstract class BatteryStats implements Parcelable {
public static final int VIBRATOR_ON = 9;
/**
+ * A constant indicating a foreground activity timer
+ */
+ public static final int FOREGROUND_ACTIVITY = 10;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -125,7 +130,7 @@ public abstract class BatteryStats implements Parcelable {
/**
* Bump the version on this if the checkin format changes.
*/
- private static final int BATTERY_STATS_CHECKIN_VERSION = 5;
+ private static final int BATTERY_STATS_CHECKIN_VERSION = 6;
private static final long BYTES_PER_KB = 1024;
private static final long BYTES_PER_MB = 1048576; // 1024^2
@@ -137,6 +142,7 @@ public abstract class BatteryStats implements Parcelable {
private static final String PROCESS_DATA = "pr";
private static final String SENSOR_DATA = "sr";
private static final String VIBRATOR_DATA = "vib";
+ private static final String FOREGROUND_DATA = "fg";
private static final String WAKELOCK_DATA = "wl";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
private static final String NETWORK_DATA = "nt";
@@ -276,6 +282,8 @@ public abstract class BatteryStats implements Parcelable {
public abstract void noteAudioTurnedOffLocked();
public abstract void noteVideoTurnedOnLocked();
public abstract void noteVideoTurnedOffLocked();
+ public abstract void noteActivityResumedLocked();
+ public abstract void noteActivityPausedLocked();
public abstract long getWifiRunningTime(long batteryRealtime, int which);
public abstract long getFullWifiLockTime(long batteryRealtime, int which);
public abstract long getWifiScanTime(long batteryRealtime, int which);
@@ -283,6 +291,7 @@ public abstract class BatteryStats implements Parcelable {
int which);
public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
+ public abstract Timer getForegroundActivityTimer();
public abstract Timer getVibratorOnTimer();
/**
@@ -1229,7 +1238,7 @@ public abstract class BatteryStats implements Parcelable {
final int NU = uidStats.size();
String category = STAT_NAMES[which];
-
+
// Dump "battery" stat
dumpLine(pw, 0 /* uid */, category, BATTERY_DATA,
which == STATS_SINCE_CHARGED ? getStartCount() : "N/A",
@@ -1417,22 +1426,31 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ Timer fgTimer = u.getForegroundActivityTimer();
+ if (fgTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ int count = fgTimer.getCountLocked(which);
+ if (totalTime != 0) {
+ dumpLine(pw, uid, category, FOREGROUND_DATA, totalTime, count);
+ }
+ }
+
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
: processStats.entrySet()) {
Uid.Proc ps = ent.getValue();
-
- long userTime = ps.getUserTime(which);
- long systemTime = ps.getSystemTime(which);
- int starts = ps.getStarts(which);
-
- if (userTime != 0 || systemTime != 0 || starts != 0) {
- dumpLine(pw, uid, category, PROCESS_DATA,
- ent.getKey(), // proc
- userTime * 10, // cpu time in ms
- systemTime * 10, // user time in ms
- starts); // process starts
+
+ final long userMillis = ps.getUserTime(which) * 10;
+ final long systemMillis = ps.getSystemTime(which) * 10;
+ final long foregroundMillis = ps.getForegroundTime(which) * 10;
+ final long starts = ps.getStarts(which);
+
+ if (userMillis != 0 || systemMillis != 0 || foregroundMillis != 0
+ || starts != 0) {
+ dumpLine(pw, uid, category, PROCESS_DATA, ent.getKey(), userMillis,
+ systemMillis, foregroundMillis, starts);
}
}
}
@@ -1961,6 +1979,24 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ Timer fgTimer = u.getForegroundActivityTimer();
+ if (fgTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ int count = fgTimer.getCountLocked(which);
+ if (totalTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Foreground activities: ");
+ formatTimeMs(sb, totalTime);
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
@@ -1968,23 +2004,26 @@ public abstract class BatteryStats implements Parcelable {
Uid.Proc ps = ent.getValue();
long userTime;
long systemTime;
+ long foregroundTime;
int starts;
int numExcessive;
userTime = ps.getUserTime(which);
systemTime = ps.getSystemTime(which);
+ foregroundTime = ps.getForegroundTime(which);
starts = ps.getStarts(which);
numExcessive = which == STATS_SINCE_CHARGED
? ps.countExcessivePowers() : 0;
- if (userTime != 0 || systemTime != 0 || starts != 0
+ if (userTime != 0 || systemTime != 0 || foregroundTime != 0 || starts != 0
|| numExcessive != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Proc ");
sb.append(ent.getKey()); sb.append(":\n");
sb.append(prefix); sb.append(" CPU: ");
formatTime(sb, userTime); sb.append("usr + ");
- formatTime(sb, systemTime); sb.append("krn");
+ formatTime(sb, systemTime); sb.append("krn ; ");
+ formatTime(sb, foregroundTime); sb.append("fg");
if (starts != 0) {
sb.append("\n"); sb.append(prefix); sb.append(" ");
sb.append(starts); sb.append(" proc starts");
@@ -2042,7 +2081,7 @@ public abstract class BatteryStats implements Parcelable {
sb.append(sent.getKey()); sb.append(":\n");
sb.append(prefix); sb.append(" Created for: ");
formatTimeMs(sb, startTime / 1000);
- sb.append(" uptime\n");
+ sb.append("uptime\n");
sb.append(prefix); sb.append(" Starts: ");
sb.append(starts);
sb.append(", launches: "); sb.append(launches);
@@ -2215,7 +2254,7 @@ public abstract class BatteryStats implements Parcelable {
* @param pw a Printer to receive the dump output.
*/
@SuppressWarnings("unused")
- public void dumpLocked(PrintWriter pw) {
+ public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly) {
prepareForDumpLocked();
long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
@@ -2267,28 +2306,22 @@ public abstract class BatteryStats implements Parcelable {
if (didPid) {
pw.println("");
}
-
- pw.println("Statistics since last charge:");
- pw.println(" System starts: " + getStartCount()
- + ", currently on battery: " + getIsOnBattery());
- dumpLocked(pw, "", STATS_SINCE_CHARGED, -1);
- pw.println("");
+
+ if (!isUnpluggedOnly) {
+ pw.println("Statistics since last charge:");
+ pw.println(" System starts: " + getStartCount()
+ + ", currently on battery: " + getIsOnBattery());
+ dumpLocked(pw, "", STATS_SINCE_CHARGED, -1);
+ pw.println("");
+ }
pw.println("Statistics since last unplugged:");
dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, -1);
}
@SuppressWarnings("unused")
- public void dumpCheckinLocked(PrintWriter pw, String[] args, List<ApplicationInfo> apps) {
+ public void dumpCheckinLocked(
+ PrintWriter pw, List<ApplicationInfo> apps, boolean isUnpluggedOnly) {
prepareForDumpLocked();
-
- boolean isUnpluggedOnly = false;
-
- for (String arg : args) {
- if ("-u".equals(arg)) {
- if (LOCAL_LOGV) Log.v("BatteryStats", "Dumping unplugged data");
- isUnpluggedOnly = true;
- }
- }
if (apps != null) {
SparseArray<ArrayList<String>> uids = new SparseArray<ArrayList<String>>();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 6c9f2d1..71c3e4a 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -436,6 +436,11 @@ public class Build {
* Android 4.3: Jelly Bean MR2, the revenge of the beans.
*/
public static final int JELLY_BEAN_MR2 = 18;
+
+ /**
+ * Android X.X: Key Lime Pie, another tasty treat.
+ */
+ public static final int KEY_LIME_PIE = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index fd01da9..362ae29 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -108,31 +108,78 @@ public final class Debug
* process. The returns info broken down by dalvik, native, and other. All results are in kB.
*/
public static class MemoryInfo implements Parcelable {
- /** The proportional set size for dalvik. */
+ /** The proportional set size for dalvik heap. (Doesn't include other Dalvik overhead.) */
public int dalvikPss;
- /** The private dirty pages used by dalvik. */
+ /** The proportional set size that is swappable for dalvik heap. */
+ /** @hide We may want to expose this, eventually. */
+ public int dalvikSwappablePss;
+ /** The private dirty pages used by dalvik heap. */
public int dalvikPrivateDirty;
- /** The shared dirty pages used by dalvik. */
+ /** The shared dirty pages used by dalvik heap. */
public int dalvikSharedDirty;
+ /** The private clean pages used by dalvik heap. */
+ /** @hide We may want to expose this, eventually. */
+ public int dalvikPrivateClean;
+ /** The shared clean pages used by dalvik heap. */
+ /** @hide We may want to expose this, eventually. */
+ public int dalvikSharedClean;
/** The proportional set size for the native heap. */
public int nativePss;
+ /** The proportional set size that is swappable for the native heap. */
+ /** @hide We may want to expose this, eventually. */
+ public int nativeSwappablePss;
/** The private dirty pages used by the native heap. */
public int nativePrivateDirty;
/** The shared dirty pages used by the native heap. */
public int nativeSharedDirty;
+ /** The private clean pages used by the native heap. */
+ /** @hide We may want to expose this, eventually. */
+ public int nativePrivateClean;
+ /** The shared clean pages used by the native heap. */
+ /** @hide We may want to expose this, eventually. */
+ public int nativeSharedClean;
/** The proportional set size for everything else. */
public int otherPss;
+ /** The proportional set size that is swappable for everything else. */
+ /** @hide We may want to expose this, eventually. */
+ public int otherSwappablePss;
/** The private dirty pages used by everything else. */
public int otherPrivateDirty;
/** The shared dirty pages used by everything else. */
public int otherSharedDirty;
+ /** The private clean pages used by everything else. */
+ /** @hide We may want to expose this, eventually. */
+ public int otherPrivateClean;
+ /** The shared clean pages used by everything else. */
+ /** @hide We may want to expose this, eventually. */
+ public int otherSharedClean;
/** @hide */
- public static final int NUM_OTHER_STATS = 10;
+ public static final int NUM_OTHER_STATS = 13;
- private int[] otherStats = new int[NUM_OTHER_STATS*3];
+ /** @hide */
+ public static final int NUM_DVK_STATS = 5;
+
+ /** @hide */
+ public static final int NUM_CATEGORIES = 6;
+
+ /** @hide */
+ public static final int offsetPss = 0;
+ /** @hide */
+ public static final int offsetSwappablePss = 1;
+ /** @hide */
+ public static final int offsetPrivateDirty = 2;
+ /** @hide */
+ public static final int offsetSharedDirty = 3;
+ /** @hide */
+ public static final int offsetPrivateClean = 4;
+ /** @hide */
+ public static final int offsetSharedClean = 5;
+
+
+ private int[] otherStats = new int[(NUM_OTHER_STATS+NUM_DVK_STATS)*NUM_CATEGORIES];
public MemoryInfo() {
}
@@ -144,6 +191,14 @@ public final class Debug
return dalvikPss + nativePss + otherPss;
}
+
+ /**
+ * Return total PSS memory usage in kB.
+ */
+ public int getTotalSwappablePss() {
+ return dalvikSwappablePss + nativeSwappablePss + otherSwappablePss;
+ }
+
/**
* Return total private dirty memory usage in kB.
*/
@@ -158,35 +213,74 @@ public final class Debug
return dalvikSharedDirty + nativeSharedDirty + otherSharedDirty;
}
+ /**
+ * Return total shared clean memory usage in kB.
+ */
+ public int getTotalPrivateClean() {
+ return dalvikPrivateClean + nativePrivateClean + otherPrivateClean;
+ }
+
+ /**
+ * Return total shared clean memory usage in kB.
+ */
+ public int getTotalSharedClean() {
+ return dalvikSharedClean + nativeSharedClean + otherSharedClean;
+ }
+
/* @hide */
public int getOtherPss(int which) {
- return otherStats[which*3];
+ return otherStats[which*NUM_CATEGORIES + offsetPss];
+ }
+
+
+ /* @hide */
+ public int getOtherSwappablePss(int which) {
+ return otherStats[which*NUM_CATEGORIES + offsetSwappablePss];
}
+
/* @hide */
public int getOtherPrivateDirty(int which) {
- return otherStats[which*3 + 1];
+ return otherStats[which*NUM_CATEGORIES + offsetPrivateDirty];
}
/* @hide */
public int getOtherSharedDirty(int which) {
- return otherStats[which*3 + 2];
+ return otherStats[which*NUM_CATEGORIES + offsetSharedDirty];
}
+ /* @hide */
+ public int getOtherPrivateClean(int which) {
+ return otherStats[which*NUM_CATEGORIES + offsetPrivateClean];
+ }
+
+
+ /* @hide */
+ public int getOtherSharedClean(int which) {
+ return otherStats[which*NUM_CATEGORIES + offsetSharedClean];
+ }
/* @hide */
public static String getOtherLabel(int which) {
switch (which) {
- case 0: return "Stack";
- case 1: return "Cursor";
- case 2: return "Ashmem";
- case 3: return "Other dev";
- case 4: return ".so mmap";
- case 5: return ".jar mmap";
- case 6: return ".apk mmap";
- case 7: return ".ttf mmap";
- case 8: return ".dex mmap";
- case 9: return "Other mmap";
+ case 0: return "Dalvik Other";
+ case 1: return "Stack";
+ case 2: return "Cursor";
+ case 3: return "Ashmem";
+ case 4: return "Other dev";
+ case 5: return ".so mmap";
+ case 6: return ".jar mmap";
+ case 7: return ".apk mmap";
+ case 8: return ".ttf mmap";
+ case 9: return ".dex mmap";
+ case 10: return "code mmap";
+ case 11: return "image mmap";
+ case 12: return "Other mmap";
+ case 13: return ".Heap";
+ case 14: return ".LOS";
+ case 15: return ".LinearAlloc";
+ case 16: return ".GC";
+ case 17: return ".JITCache";
default: return "????";
}
}
@@ -197,27 +291,45 @@ public final class Debug
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(dalvikPss);
+ dest.writeInt(dalvikSwappablePss);
dest.writeInt(dalvikPrivateDirty);
dest.writeInt(dalvikSharedDirty);
+ dest.writeInt(dalvikPrivateClean);
+ dest.writeInt(dalvikSharedClean);
dest.writeInt(nativePss);
+ dest.writeInt(nativeSwappablePss);
dest.writeInt(nativePrivateDirty);
dest.writeInt(nativeSharedDirty);
+ dest.writeInt(nativePrivateClean);
+ dest.writeInt(nativeSharedClean);
dest.writeInt(otherPss);
+ dest.writeInt(otherSwappablePss);
dest.writeInt(otherPrivateDirty);
dest.writeInt(otherSharedDirty);
+ dest.writeInt(otherPrivateClean);
+ dest.writeInt(otherSharedClean);
dest.writeIntArray(otherStats);
}
public void readFromParcel(Parcel source) {
dalvikPss = source.readInt();
+ dalvikSwappablePss = source.readInt();
dalvikPrivateDirty = source.readInt();
dalvikSharedDirty = source.readInt();
+ dalvikPrivateClean = source.readInt();
+ dalvikSharedClean = source.readInt();
nativePss = source.readInt();
+ nativeSwappablePss = source.readInt();
nativePrivateDirty = source.readInt();
nativeSharedDirty = source.readInt();
+ nativePrivateClean = source.readInt();
+ nativeSharedClean = source.readInt();
otherPss = source.readInt();
+ otherSwappablePss = source.readInt();
otherPrivateDirty = source.readInt();
otherSharedDirty = source.readInt();
+ otherPrivateClean = source.readInt();
+ otherSharedClean = source.readInt();
otherStats = source.createIntArray();
}
@@ -1397,7 +1509,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Return a String describing the calling method and location at a particular stack depth.
- * @param callStack the Thread stack
+ * @param callStack the Thread stack
* @param depth the depth of stack to return information for.
* @return the String describing the caller at that depth.
*/
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 61eef1f..70a1edc 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -19,6 +19,7 @@ package android.os;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
@@ -59,6 +60,10 @@ public class Environment {
private static volatile StorageVolume sPrimaryVolume;
private static StorageVolume getPrimaryVolume() {
+ if (SystemProperties.getBoolean("config.disable_storage", false)) {
+ return null;
+ }
+
if (sPrimaryVolume == null) {
synchronized (sLock) {
if (sPrimaryVolume == null) {
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 6d6d147..23492ff 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -25,7 +25,7 @@ interface IPowerManager
{
// WARNING: The first two methods must remain the first two methods because their
// transaction numbers must not change unless IPowerManager.cpp is also updated.
- void acquireWakeLock(IBinder lock, int flags, String tag, in WorkSource ws);
+ void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws);
void releaseWakeLock(IBinder lock, int flags);
void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index d5cf771..78c859e 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -289,6 +289,16 @@ public final class Looper {
return mQueue;
}
+ /**
+ * Return whether this looper's thread is currently idle, waiting for new work
+ * to do. This is intrinsically racy, since its state can change before you get
+ * the result back.
+ * @hide
+ */
+ public boolean isIdling() {
+ return mQueue.isIdling();
+ }
+
public void dump(Printer pw, String prefix) {
pw = PrefixPrinter.create(pw, prefix);
pw.println(this.toString());
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index bf7e5ca..1e8983e 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -52,6 +52,7 @@ public final class MessageQueue {
private native static void nativeDestroy(int ptr);
private native static void nativePollOnce(int ptr, int timeoutMillis);
private native static void nativeWake(int ptr);
+ private native static boolean nativeIsIdling(int ptr);
/**
* Callback interface for discovering when a thread is going to block
@@ -379,6 +380,10 @@ public final class MessageQueue {
}
}
+ boolean isIdling() {
+ return nativeIsIdling(mPtr);
+ }
+
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
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/PowerManager.java b/core/java/android/os/PowerManager.java
index 736762f..52e5f38 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -224,7 +224,7 @@ public final class PowerManager {
/**
* Flag for {@link WakeLock#release release(int)} to defer releasing a
- * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wake lock until the proximity sensor returns
+ * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor returns
* a negative value.
*
* {@hide}
@@ -407,7 +407,7 @@ public final class PowerManager {
*/
public WakeLock newWakeLock(int levelAndFlags, String tag) {
validateWakeLockParameters(levelAndFlags, tag);
- return new WakeLock(levelAndFlags, tag);
+ return new WakeLock(levelAndFlags, tag, mContext.getBasePackageName());
}
/** @hide */
@@ -624,6 +624,7 @@ public final class PowerManager {
public final class WakeLock {
private final int mFlags;
private final String mTag;
+ private final String mPackageName;
private final IBinder mToken;
private int mCount;
private boolean mRefCounted = true;
@@ -636,9 +637,10 @@ public final class PowerManager {
}
};
- WakeLock(int flags, String tag) {
+ WakeLock(int flags, String tag, String packageName) {
mFlags = flags;
mTag = tag;
+ mPackageName = packageName;
mToken = new Binder();
}
@@ -714,7 +716,7 @@ public final class PowerManager {
// been explicitly released by the keyguard.
mHandler.removeCallbacks(mReleaser);
try {
- mService.acquireWakeLock(mToken, mFlags, mTag, mWorkSource);
+ mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
} catch (RemoteException e) {
}
mHeld = true;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 476b4ea..93b1255 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -650,6 +650,12 @@ public class Process {
public static final native int myPid();
/**
+ * Returns the identifier of this process' parent.
+ * @hide
+ */
+ public static native int myPpid();
+
+ /**
* Returns the identifier of the calling thread, which be used with
* {@link #setThreadPriority(int, int)}.
*/
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 480fe7d..5e20dec 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -244,12 +244,17 @@ public class RecoverySystem {
// The signature cert matches a trusted key. Now verify that
// the digest in the cert matches the actual file data.
- // The verifier in recovery *only* handles SHA1withRSA
- // signatures. SignApk.java always uses SHA1withRSA, no
- // matter what the cert says to use. Ignore
- // cert.getSigAlgName(), and instead use whatever
- // algorithm is used by the signature (which should be
- // SHA1withRSA).
+ // The verifier in recovery only handles SHA1withRSA and
+ // SHA256withRSA signatures. SignApk chooses which to use
+ // based on the signature algorithm of the cert:
+ //
+ // "SHA256withRSA" cert -> "SHA256withRSA" signature
+ // "SHA1withRSA" cert -> "SHA1withRSA" signature
+ // "MD5withRSA" cert -> "SHA1withRSA" signature (for backwards compatibility)
+ // any other cert -> SignApk fails
+ //
+ // Here we ignore whatever the cert says, and instead use
+ // whatever algorithm is used by the signature.
String da = sigInfo.getDigestAlgorithm();
String dea = sigInfo.getDigestEncryptionAlgorithm();
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index d02a320..c1d4ae9 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -16,6 +16,8 @@
package android.os;
+import android.util.ArrayMap;
+
import java.util.HashMap;
/**
@@ -47,8 +49,8 @@ import java.util.HashMap;
* implements the {@link #onCallbackDied} method.
*/
public class RemoteCallbackList<E extends IInterface> {
- /*package*/ HashMap<IBinder, Callback> mCallbacks
- = new HashMap<IBinder, Callback>();
+ /*package*/ ArrayMap<IBinder, Callback> mCallbacks
+ = new ArrayMap<IBinder, Callback>();
private Object[] mActiveBroadcast;
private int mBroadcastCount = -1;
private boolean mKilled = false;
@@ -159,7 +161,8 @@ public class RemoteCallbackList<E extends IInterface> {
*/
public void kill() {
synchronized (mCallbacks) {
- for (Callback cb : mCallbacks.values()) {
+ for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
+ Callback cb = mCallbacks.valueAt(cbi);
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
}
mCallbacks.clear();
@@ -238,11 +241,10 @@ public class RemoteCallbackList<E extends IInterface> {
if (active == null || active.length < N) {
mActiveBroadcast = active = new Object[N];
}
- int i=0;
- for (Callback cb : mCallbacks.values()) {
- active[i++] = cb;
+ for (int i=0; i<N; i++) {
+ active[i] = mCallbacks.valueAt(i);
}
- return i;
+ return N;
}
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 3267939..31da091 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -24,6 +24,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Printer;
import android.util.Singleton;
@@ -1069,7 +1070,7 @@ public final class StrictMode {
// Map from violation stacktrace hashcode -> uptimeMillis of
// last violation. No locking needed, as this is only
// accessed by the same thread.
- private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>();
+ private ArrayMap<Integer, Long> mLastViolationTime;
public AndroidBlockGuardPolicy(final int policyMask) {
mPolicyMask = policyMask;
@@ -1279,8 +1280,13 @@ public final class StrictMode {
// Not perfect, but fast and good enough for dup suppression.
Integer crashFingerprint = info.hashCode();
long lastViolationTime = 0;
- if (mLastViolationTime.containsKey(crashFingerprint)) {
- lastViolationTime = mLastViolationTime.get(crashFingerprint);
+ if (mLastViolationTime != null) {
+ Long vtime = mLastViolationTime.get(crashFingerprint);
+ if (vtime != null) {
+ lastViolationTime = vtime;
+ }
+ } else {
+ mLastViolationTime = new ArrayMap<Integer, Long>(1);
}
long now = SystemClock.uptimeMillis();
mLastViolationTime.put(crashFingerprint, now);
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index e53cb5e..bb3d296 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -67,6 +67,8 @@ public final class Trace {
public static final long TRACE_TAG_RESOURCES = 1L << 13;
/** @hide */
public static final long TRACE_TAG_DALVIK = 1L << 14;
+ /** @hide */
+ public static final long TRACE_TAG_RS = 1L << 15;
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/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
new file mode 100644
index 0000000..c26f6d4
--- /dev/null
+++ b/core/java/android/provider/DocumentsContract.java
@@ -0,0 +1,209 @@
+/*
+ * 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.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The contract between a storage backend and the platform. Contains definitions
+ * for the supported URIs and columns.
+ */
+public final class DocumentsContract {
+ private static final String TAG = "Documents";
+
+ // content://com.example/docs/0/
+ // content://com.example/docs/0/contents/
+ // content://com.example/search/?query=pony
+
+ /**
+ * MIME type of a document which is a directory that may contain additional
+ * documents.
+ *
+ * @see #buildContentsUri(Uri)
+ */
+ public static final String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";
+
+ /** {@hide} */
+ public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
+
+ /**
+ * {@link DocumentColumns#GUID} value representing the root directory of a
+ * storage backend.
+ */
+ public static final String ROOT_GUID = "0";
+
+ /**
+ * Flag indicating that a document is a directory that supports creation of
+ * new files within it.
+ *
+ * @see DocumentColumns#FLAGS
+ * @see #buildContentsUri(Uri)
+ */
+ public static final int FLAG_SUPPORTS_CREATE = 1;
+
+ /**
+ * Flag indicating that a document is renamable.
+ *
+ * @see DocumentColumns#FLAGS
+ * @see #renameDocument(ContentResolver, Uri, String)
+ */
+ public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
+
+ /**
+ * Flag indicating that a document can be represented as a thumbnail.
+ *
+ * @see DocumentColumns#FLAGS
+ * @see #getThumbnail(ContentResolver, Uri, Point)
+ */
+ public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 2;
+
+ /**
+ * Optimal dimensions for a document thumbnail request, stored as a
+ * {@link Point} object. This is only a hint, and the returned thumbnail may
+ * have different dimensions.
+ */
+ public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+
+ private static final String PATH_DOCS = "docs";
+ private static final String PATH_CONTENTS = "contents";
+ private static final String PATH_SEARCH = "search";
+
+ private static final String PARAM_QUERY = "query";
+
+ /**
+ * Build URI representing the given {@link DocumentColumns#GUID} in a
+ * storage backend.
+ */
+ public static Uri buildDocumentUri(String authority, String guid) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_DOCS).appendPath(guid).build();
+ }
+
+ /**
+ * Build URI representing a search for matching documents in a storage
+ * backend.
+ */
+ public static Uri buildSearchUri(String authority, String query) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
+ }
+
+ /**
+ * Build URI representing the contents of the given directory in a storage
+ * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}.
+ */
+ public static Uri buildContentsUri(Uri documentUri) {
+ return documentUri.buildUpon().appendPath(PATH_CONTENTS).build();
+ }
+
+ /**
+ * These are standard columns for document URIs. Storage backend providers
+ * <em>must</em> support at least these columns when queried.
+ *
+ * @see Intent#ACTION_OPEN_DOCUMENT
+ * @see Intent#ACTION_CREATE_DOCUMENT
+ */
+ public interface DocumentColumns extends OpenableColumns {
+ /**
+ * The globally unique ID for a document within a storage backend.
+ * Values <em>must</em> never change once returned.
+ * <p>
+ * Type: STRING
+ *
+ * @see DocumentsContract#ROOT_GUID
+ */
+ public static final String GUID = "guid";
+
+ /**
+ * MIME type of a document, matching the value returned by
+ * {@link ContentResolver#getType(android.net.Uri)}.
+ * <p>
+ * Type: STRING
+ *
+ * @see DocumentsContract#MIME_TYPE_DIRECTORY
+ */
+ public static final String MIME_TYPE = "mime_type";
+
+ /**
+ * Timestamp when a document was last modified, in milliseconds since
+ * January 1, 1970 00:00:00.0 UTC.
+ * <p>
+ * Type: INTEGER (long)
+ *
+ * @see System#currentTimeMillis()
+ */
+ public static final String LAST_MODIFIED = "last_modified";
+
+ /**
+ * Flags that apply to a specific document.
+ * <p>
+ * Type: INTEGER (int)
+ */
+ public static final String FLAGS = "flags";
+ }
+
+ /**
+ * Return thumbnail representing the document at the given URI. Callers are
+ * responsible for their own caching. Given document must have
+ * {@link #FLAG_SUPPORTS_THUMBNAIL} set.
+ *
+ * @return decoded thumbnail, or {@code null} if problem was encountered.
+ */
+ public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
+ final Bundle opts = new Bundle();
+ opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size);
+
+ InputStream is = null;
+ try {
+ is = new AssetFileDescriptor.AutoCloseInputStream(
+ resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts));
+ return BitmapFactory.decodeStream(is);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
+ return null;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+
+ /**
+ * Rename the document at the given URI. Given document must have
+ * {@link #FLAG_SUPPORTS_RENAME} set.
+ *
+ * @return if rename was successful.
+ */
+ public static boolean renameDocument(
+ ContentResolver resolver, Uri documentUri, String displayName) {
+ final ContentValues values = new ContentValues();
+ values.put(DocumentColumns.DISPLAY_NAME, displayName);
+ return (resolver.update(documentUri, values, null, null) == 1);
+ }
+}
diff --git a/core/java/android/provider/DrmStore.java b/core/java/android/provider/DrmStore.java
deleted file mode 100644
index 34f2f0d..0000000
--- a/core/java/android/provider/DrmStore.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2008 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.provider;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.drm.mobile1.DrmRawContent;
-import android.drm.mobile1.DrmRights;
-import android.drm.mobile1.DrmRightsManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * The DRM provider contains forward locked DRM content.
- *
- * @hide
- */
-public final class DrmStore
-{
- private static final String TAG = "DrmStore";
-
- public static final String AUTHORITY = "drm";
-
- /**
- * This is in the Manifest class of the drm provider, but that isn't visible
- * in the framework.
- */
- private static final String ACCESS_DRM_PERMISSION = "android.permission.ACCESS_DRM";
-
- /**
- * Fields for DRM database
- */
-
- public interface Columns extends BaseColumns {
- /**
- * The data stream for the file
- * <P>Type: DATA STREAM</P>
- */
- public static final String DATA = "_data";
-
- /**
- * The size of the file in bytes
- * <P>Type: INTEGER (long)</P>
- */
- public static final String SIZE = "_size";
-
- /**
- * The title of the file content
- * <P>Type: TEXT</P>
- */
- public static final String TITLE = "title";
-
- /**
- * The MIME type of the file
- * <P>Type: TEXT</P>
- */
- public static final String MIME_TYPE = "mime_type";
-
- }
-
- public interface Images extends Columns {
-
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/images");
- }
-
- public interface Audio extends Columns {
-
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/audio");
- }
-
- /**
- * Utility function for inserting a file into the DRM content provider.
- *
- * @param cr The content resolver to use
- * @param file The file to insert
- * @param title The title for the content (or null)
- * @return uri to the DRM record or null
- */
- public static final Intent addDrmFile(ContentResolver cr, File file, String title) {
- FileInputStream fis = null;
- Intent result = null;
-
- try {
- fis = new FileInputStream(file);
- if (title == null) {
- title = file.getName();
- int lastDot = title.lastIndexOf('.');
- if (lastDot > 0) {
- title = title.substring(0, lastDot);
- }
- }
- result = addDrmFile(cr, fis, title);
- } catch (Exception e) {
- Log.e(TAG, "pushing file failed", e);
- } finally {
- try {
- if (fis != null)
- fis.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in DrmStore.addDrmFile()", e);
- }
- }
-
- return result;
- }
-
- /**
- * Utility function for inserting a file stream into the DRM content provider.
- *
- * @param cr The content resolver to use
- * @param fis The FileInputStream to insert
- * @param title The title for the content (or null)
- * @return uri to the DRM record or null
- */
- public static final Intent addDrmFile(ContentResolver cr, FileInputStream fis, String title) {
- OutputStream os = null;
- Intent result = null;
-
- try {
- DrmRawContent content = new DrmRawContent(fis, (int) fis.available(),
- DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING);
- String mimeType = content.getContentType();
- long size = fis.getChannel().size();
-
- DrmRightsManager manager = manager = DrmRightsManager.getInstance();
- DrmRights rights = manager.queryRights(content);
- InputStream stream = content.getContentInputStream(rights);
-
- Uri contentUri = null;
- if (mimeType.startsWith("audio/")) {
- contentUri = DrmStore.Audio.CONTENT_URI;
- } else if (mimeType.startsWith("image/")) {
- contentUri = DrmStore.Images.CONTENT_URI;
- } else {
- Log.w(TAG, "unsupported mime type " + mimeType);
- }
-
- if (contentUri != null) {
- ContentValues values = new ContentValues(3);
- values.put(DrmStore.Columns.TITLE, title);
- values.put(DrmStore.Columns.SIZE, size);
- values.put(DrmStore.Columns.MIME_TYPE, mimeType);
-
- Uri uri = cr.insert(contentUri, values);
- if (uri != null) {
- os = cr.openOutputStream(uri);
-
- byte[] buffer = new byte[1000];
- int count;
-
- while ((count = stream.read(buffer)) != -1) {
- os.write(buffer, 0, count);
- }
- result = new Intent();
- result.setDataAndType(uri, mimeType);
-
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "pushing file failed", e);
- } finally {
- try {
- if (fis != null)
- fis.close();
- if (os != null)
- os.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in DrmStore.addDrmFile()", e);
- }
- }
-
- return result;
- }
-
- /**
- * Utility function to enforce any permissions required to access DRM
- * content.
- *
- * @param context A context used for checking calling permission.
- */
- public static void enforceAccessDrmPermission(Context context) {
- if (context.checkCallingOrSelfPermission(ACCESS_DRM_PERMISSION) !=
- PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires DRM permission");
- }
- }
-
-}
diff --git a/core/java/android/provider/OpenableColumns.java b/core/java/android/provider/OpenableColumns.java
index f548bae..faf96b7 100644
--- a/core/java/android/provider/OpenableColumns.java
+++ b/core/java/android/provider/OpenableColumns.java
@@ -16,11 +16,17 @@
package android.provider;
+import android.content.ContentResolver;
+import android.content.Intent;
+
/**
- * These are standard columns for openable URIs. (See
- * {@link android.content.Intent#CATEGORY_OPENABLE}.) If possible providers that have openable URIs
- * should support these columns. To find the content type of a URI use
- * {@link android.content.ContentResolver#getType(android.net.Uri)} as normal.
+ * These are standard columns for openable URIs. Providers that serve openable
+ * URIs <em>must</em> support at least these columns when queried.
+ * <p>
+ * To find the content type of a URI, use
+ * {@link ContentResolver#getType(android.net.Uri)}.
+ *
+ * @see Intent#CATEGORY_OPENABLE
*/
public interface OpenableColumns {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3c8187e..c1a296d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1010,6 +1010,14 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.WAIT_FOR_DEBUGGER);
MOVED_TO_GLOBAL.add(Settings.Global.SHOW_PROCESSES);
MOVED_TO_GLOBAL.add(Settings.Global.ALWAYS_FINISH_ACTIVITIES);
+ MOVED_TO_GLOBAL.add(Settings.Global.TZINFO_UPDATE_CONTENT_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.TZINFO_UPDATE_METADATA_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.SELINUX_UPDATE_CONTENT_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.SELINUX_UPDATE_METADATA_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_CONTENT_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_METADATA_URL);
}
/** @hide */
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index 2706bc7..5fbd22e 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -355,7 +355,18 @@ public class TtsEngines {
return v1Locale;
}
- private String getDefaultLocale() {
+ /**
+ * Return the default device locale in form of 3 letter codes delimited by
+ * {@link #LOCALE_DELIMITER}:
+ * <ul>
+ * <li> "ISO 639-2/T language code" if locale have no country entry</li>
+ * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code "
+ * if locale have no variant entry</li>
+ * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code
+ * {@link #LOCALE_DELIMITER} variant" if locale have variant entry</li>
+ * </ul>
+ */
+ public String getDefaultLocale() {
final Locale locale = Locale.getDefault();
// Note that the default locale might have an empty variant
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 30bb447..ba6f1d4 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -56,7 +56,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0
&& MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0) {
+ MetaKeyKeyListener.META_SELECTING, event) != 0) {
return widget.showContextMenu();
}
}
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 4fede32..63607fa 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -75,7 +75,7 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener
}
// Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
- if (event.isAltPressed() || getMetaState(content, META_ALT_ON) == 1) {
+ if (getMetaState(content, META_ALT_ON, event) == 1) {
if (deleteLine(view, content)) {
return true;
}
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
index 113a4be..155a2c4 100644
--- a/core/java/android/text/method/BaseMovementMethod.java
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -135,7 +135,7 @@ public class BaseMovementMethod implements MovementMethod {
*/
protected int getMovementMetaState(Spannable buffer, KeyEvent event) {
// We ignore locked modifiers and SHIFT.
- int metaState = (event.getMetaState() | MetaKeyKeyListener.getMetaState(buffer))
+ int metaState = MetaKeyKeyListener.getMetaState(buffer, event)
& ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED);
return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK;
}
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
index ce51fae..bb8b0de 100644
--- a/core/java/android/text/method/DialerKeyListener.java
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -53,7 +53,7 @@ public class DialerKeyListener extends NumberKeyListener
* from the KeyEvent.
*/
protected int lookup(KeyEvent event, Spannable content) {
- int meta = event.getMetaState() | getMetaState(content);
+ int meta = getMetaState(content, event);
int number = event.getNumber();
/*
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/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index 0a097f9..e9db5fd 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -135,6 +135,9 @@ public abstract class MetaKeyKeyListener {
private static final Object SYM = new NoCopySpan.Concrete();
private static final Object SELECTING = new NoCopySpan.Concrete();
+ private static final int PRESSED_RETURN_VALUE = 1;
+ private static final int LOCKED_RETURN_VALUE = 2;
+
/**
* Resets all meta state to inactive.
*/
@@ -161,9 +164,34 @@ public abstract class MetaKeyKeyListener {
}
/**
+ * Gets the state of the meta keys for a specific key event.
+ *
+ * For input devices that use toggled key modifiers, the `toggled' state
+ * is stored into the text buffer. This method retrieves the meta state
+ * for this event, accounting for the stored state. If the event has been
+ * created by a device that does not support toggled key modifiers, like
+ * a virtual device for example, the stored state is ignored.
+ *
+ * @param text the buffer in which the meta key would have been pressed.
+ * @param event the event for which to evaluate the meta state.
+ * @return an integer in which each bit set to one represents a pressed
+ * or locked meta key.
+ */
+ public static final int getMetaState(final CharSequence text, final KeyEvent event) {
+ int metaState = event.getMetaState();
+ if (event.getKeyCharacterMap().getModifierBehavior()
+ == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
+ metaState |= getMetaState(text);
+ }
+ return metaState;
+ }
+
+ // As META_SELECTING is @hide we should not mention it in public comments, hence the
+ // omission in @param meta
+ /**
* Gets the state of a particular meta key.
*
- * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
+ * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
* @param text the buffer in which the meta key would have been pressed.
*
* @return 0 if inactive, 1 if active, 2 if locked.
@@ -171,22 +199,53 @@ public abstract class MetaKeyKeyListener {
public static final int getMetaState(CharSequence text, int meta) {
switch (meta) {
case META_SHIFT_ON:
- return getActive(text, CAP, 1, 2);
+ return getActive(text, CAP, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
case META_ALT_ON:
- return getActive(text, ALT, 1, 2);
+ return getActive(text, ALT, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
case META_SYM_ON:
- return getActive(text, SYM, 1, 2);
+ return getActive(text, SYM, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
case META_SELECTING:
- return getActive(text, SELECTING, 1, 2);
+ return getActive(text, SELECTING, PRESSED_RETURN_VALUE, LOCKED_RETURN_VALUE);
default:
return 0;
}
}
+ /**
+ * Gets the state of a particular meta key to use with a particular key event.
+ *
+ * If the key event has been created by a device that does not support toggled
+ * key modifiers, like a virtual keyboard for example, only the meta state in
+ * the key event is considered.
+ *
+ * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
+ * @param text the buffer in which the meta key would have been pressed.
+ * @param event the event for which to evaluate the meta state.
+ * @return 0 if inactive, 1 if active, 2 if locked.
+ */
+ public static final int getMetaState(final CharSequence text, final int meta,
+ final KeyEvent event) {
+ int metaState = event.getMetaState();
+ if (event.getKeyCharacterMap().getModifierBehavior()
+ == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
+ metaState |= getMetaState(text);
+ }
+ if (META_SELECTING == meta) {
+ // #getMetaState(long, int) does not support META_SELECTING, but we want the same
+ // behavior as #getMetaState(CharSequence, int) so we need to do it here
+ if ((metaState & META_SELECTING) != 0) {
+ // META_SELECTING is only ever set to PRESSED and can't be LOCKED, so return 1
+ return 1;
+ }
+ return 0;
+ }
+ return getMetaState(metaState, meta);
+ }
+
private static int getActive(CharSequence text, Object meta,
int on, int lock) {
if (!(text instanceof Spanned)) {
@@ -430,18 +489,18 @@ public abstract class MetaKeyKeyListener {
public static final int getMetaState(long state, int meta) {
switch (meta) {
case META_SHIFT_ON:
- if ((state & META_CAP_LOCKED) != 0) return 2;
- if ((state & META_SHIFT_ON) != 0) return 1;
+ if ((state & META_CAP_LOCKED) != 0) return LOCKED_RETURN_VALUE;
+ if ((state & META_SHIFT_ON) != 0) return PRESSED_RETURN_VALUE;
return 0;
case META_ALT_ON:
- if ((state & META_ALT_LOCKED) != 0) return 2;
- if ((state & META_ALT_ON) != 0) return 1;
+ if ((state & META_ALT_LOCKED) != 0) return LOCKED_RETURN_VALUE;
+ if ((state & META_ALT_ON) != 0) return PRESSED_RETURN_VALUE;
return 0;
case META_SYM_ON:
- if ((state & META_SYM_LOCKED) != 0) return 2;
- if ((state & META_SYM_ON) != 0) return 1;
+ if ((state & META_SYM_LOCKED) != 0) return LOCKED_RETURN_VALUE;
+ if ((state & META_SYM_ON) != 0) return PRESSED_RETURN_VALUE;
return 0;
default:
@@ -599,4 +658,3 @@ public abstract class MetaKeyKeyListener {
private static final int LOCKED =
Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
}
-
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index 5d4c732..988d566 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -41,7 +41,7 @@ public abstract class NumberKeyListener extends BaseKeyListener
protected abstract char[] getAcceptedChars();
protected int lookup(KeyEvent event, Spannable content) {
- return event.getMatch(getAcceptedChars(), event.getMetaState() | getMetaState(content));
+ return event.getMatch(getAcceptedChars(), getMetaState(content, event));
}
public CharSequence filter(CharSequence source, int start, int end,
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index 98316ae..0bd46bc 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -108,7 +108,7 @@ public class QwertyKeyListener extends BaseKeyListener {
// QWERTY keyboard normal case
- int i = event.getUnicodeChar(event.getMetaState() | getMetaState(content));
+ int i = event.getUnicodeChar(getMetaState(content, event));
if (!mFullKeyboard) {
int count = event.getRepeatCount();
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
new file mode 100644
index 0000000..6da7546
--- /dev/null
+++ b/core/java/android/util/ArrayMap.java
@@ -0,0 +1,617 @@
+/*
+ * 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.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ArrayMap is a generic key->value mapping data structure that is
+ * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
+ * It keeps its mappings in an array data structure -- an integer array of hash
+ * codes for each item, and an Object array of the key/value pairs. This allows it to
+ * avoid having to create an extra object for every entry put in to the map, and it
+ * also tries to control the growth of the size of these arrays more aggressively
+ * (since growing them only requires copying the entries in the array, not rebuilding
+ * a hash map).
+ *
+ * <p>Note that this implementation is not intended to be appropriate for data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%. For larger numbers of items
+ * this data structure should be avoided.</p>
+ *
+ * <p><b>Note:</b> unlike {@link java.util.HashMap}, this container does not support
+ * null keys.</p>
+ *
+ * <p>Because this container is intended to better balance memory use, unlike most other
+ * standard Java containers it will shrink its array as items are removed from it. Currently
+ * you have no control over this shrinking -- if you set a capacity and then remove an
+ * item, it may reduce the capacity to better match the current size. In the future an
+ * explicitly call to set the capacity should turn off this aggressive shrinking behavior.</p>
+ *
+ * @hide
+ */
+public final class ArrayMap<K, V> implements Map<K, V> {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ArrayMap";
+
+ /**
+ * The minimum amount by which the capacity of a ArrayMap will increase.
+ * This is tuned to be relatively space-efficient.
+ */
+ private static final int BASE_SIZE = 4;
+
+ /**
+ * Maximum number of entries to have in array caches.
+ */
+ private static final int CACHE_SIZE = 10;
+
+ /**
+ * Caches of small array objects to avoid spamming garbage. The cache
+ * Object[] variable is a pointer to a linked list of array objects.
+ * The first entry in the array is a pointer to the next array in the
+ * list; the second entry is a pointer to the int[] hash code array for it.
+ */
+ static Object[] mBaseCache;
+ static int mBaseCacheSize;
+ static Object[] mTwiceBaseCache;
+ static int mTwiceBaseCacheSize;
+
+ int[] mHashes;
+ Object[] mArray;
+ int mSize;
+ MapCollections<K, V> mCollections;
+
+ private int indexOf(Object key, int hash) {
+ final int N = mSize;
+
+ // Important fast case: if nothing is in here, nothing to look for.
+ if (N == 0) {
+ return ~0;
+ }
+
+ int index = SparseArray.binarySearch(mHashes, N, hash);
+
+ // If the hash code wasn't found, then we have no entry for this key.
+ if (index < 0) {
+ return index;
+ }
+
+ // If the key at the returned index matches, that's what we want.
+ if (mArray[index<<1].equals(key)) {
+ return index;
+ }
+
+ // Search for a matching key after the index.
+ int end;
+ for (end = index + 1; end < N && mHashes[end] == hash; end++) {
+ if (mArray[end << 1].equals(key)) return end;
+ }
+
+ // Search for a matching key before the index.
+ for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
+ if (mArray[i << 1].equals(key)) return i;
+ }
+
+ // Key not found -- return negative value indicating where a
+ // new entry for this key should go. We use the end of the
+ // hash chain to reduce the number of array entries that will
+ // need to be copied when inserting.
+ return ~end;
+ }
+
+ private void allocArrays(final int size) {
+ if (size == (BASE_SIZE*2)) {
+ synchronized (ArrayMap.class) {
+ if (mTwiceBaseCache != null) {
+ final Object[] array = mTwiceBaseCache;
+ mArray = array;
+ mTwiceBaseCache = (Object[])array[0];
+ mHashes = (int[])array[1];
+ array[0] = array[1] = null;
+ mTwiceBaseCacheSize--;
+ if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
+ + " now have " + mTwiceBaseCacheSize + " entries");
+ return;
+ }
+ }
+ } else if (size == BASE_SIZE) {
+ synchronized (ArrayMap.class) {
+ if (mBaseCache != null) {
+ final Object[] array = mBaseCache;
+ mArray = array;
+ mBaseCache = (Object[])array[0];
+ mHashes = (int[])array[1];
+ array[0] = array[1] = null;
+ mBaseCacheSize--;
+ if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
+ + " now have " + mBaseCacheSize + " entries");
+ return;
+ }
+ }
+ }
+
+ mHashes = new int[size];
+ mArray = new Object[size<<1];
+ }
+
+ private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
+ if (hashes.length == (BASE_SIZE*2)) {
+ synchronized (ArrayMap.class) {
+ if (mTwiceBaseCacheSize < CACHE_SIZE) {
+ array[0] = mTwiceBaseCache;
+ array[1] = hashes;
+ for (int i=(size<<1)-1; i>=2; i--) {
+ array[i] = null;
+ }
+ mTwiceBaseCache = array;
+ mTwiceBaseCacheSize++;
+ if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
+ + " now have " + mTwiceBaseCacheSize + " entries");
+ }
+ }
+ } else if (hashes.length == BASE_SIZE) {
+ synchronized (ArrayMap.class) {
+ if (mBaseCacheSize < CACHE_SIZE) {
+ array[0] = mBaseCache;
+ array[1] = hashes;
+ for (int i=(size<<1)-1; i>=2; i--) {
+ array[i] = null;
+ }
+ mBaseCache = array;
+ mBaseCacheSize++;
+ if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
+ + " now have " + mBaseCacheSize + " entries");
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a new empty ArrayMap. The default capacity of an array map is 0, and
+ * will grow once items are added to it.
+ */
+ public ArrayMap() {
+ mHashes = SparseArray.EMPTY_INTS;
+ mArray = SparseArray.EMPTY_OBJECTS;
+ mSize = 0;
+ }
+
+ /**
+ * Create a new ArrayMap with a given initial capacity.
+ */
+ public ArrayMap(int capacity) {
+ if (capacity == 0) {
+ mHashes = SparseArray.EMPTY_INTS;
+ mArray = SparseArray.EMPTY_OBJECTS;
+ } else {
+ allocArrays(capacity);
+ }
+ mSize = 0;
+ }
+
+ /**
+ * Make the array map empty. All storage is released.
+ */
+ @Override
+ public void clear() {
+ if (mSize != 0) {
+ freeArrays(mHashes, mArray, mSize);
+ mHashes = SparseArray.EMPTY_INTS;
+ mArray = SparseArray.EMPTY_OBJECTS;
+ mSize = 0;
+ }
+ }
+
+ /**
+ * Ensure the array map can hold at least <var>minimumCapacity</var>
+ * items.
+ */
+ public void ensureCapacity(int minimumCapacity) {
+ if (mHashes.length < minimumCapacity) {
+ int[] ohashes = mHashes;
+ Object[] oarray = mArray;
+ allocArrays(minimumCapacity);
+ if (mSize > 0) {
+ System.arraycopy(ohashes, 0, mHashes, 0, mSize);
+ System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
+ }
+ freeArrays(ohashes, oarray, mSize);
+ }
+ }
+
+ /**
+ * Check whether a key exists in the array.
+ *
+ * @param key The key to search for.
+ * @return Returns true if the key exists, else false.
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ return indexOf(key, key.hashCode()) >= 0;
+ }
+
+ private int indexOfValue(Object value) {
+ final int N = mSize*2;
+ final Object[] array = mArray;
+ if (value == null) {
+ for (int i=1; i<N; i+=2) {
+ if (array[i] == null) {
+ return i>>1;
+ }
+ }
+ } else {
+ for (int i=1; i<N; i+=2) {
+ if (value.equals(array[i])) {
+ return i>>1;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Check whether a value exists in the array. This requires a linear search
+ * through the entire array.
+ *
+ * @param value The value to search for.
+ * @return Returns true if the value exists, else false.
+ */
+ @Override
+ public boolean containsValue(Object value) {
+ return indexOfValue(value) >= 0;
+ }
+
+ /**
+ * Retrieve a value from the array.
+ * @param key The key of the value to retrieve.
+ * @return Returns the value associated with the given key,
+ * or null if there is no such key.
+ */
+ @Override
+ public V get(Object key) {
+ final int index = indexOf(key, key.hashCode());
+ return index >= 0 ? (V)mArray[(index<<1)+1] : null;
+ }
+
+ /**
+ * Return the key at the given index in the array.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the key stored at the given index.
+ */
+ public K keyAt(int index) {
+ return (K)mArray[index << 1];
+ }
+
+ /**
+ * Return the value at the given index in the array.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the value stored at the given index.
+ */
+ public V valueAt(int index) {
+ return (V)mArray[(index << 1) + 1];
+ }
+
+ /**
+ * Set the value at a given index in the array.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @param value The new value to store at this index.
+ * @return Returns the previous value at the given index.
+ */
+ public V setValueAt(int index, V value) {
+ index = (index << 1) + 1;
+ V old = (V)mArray[index];
+ mArray[index] = value;
+ return old;
+ }
+
+ /**
+ * Return true if the array map contains no items.
+ */
+ @Override
+ public boolean isEmpty() {
+ return mSize <= 0;
+ }
+
+ /**
+ * Add a new value to the array map.
+ * @param key The key under which to store the value. <b>Must not be null.</b> If
+ * this key already exists in the array, its value will be replaced.
+ * @param value The value to store for the given key.
+ * @return Returns the old value that was stored for the given key, or null if there
+ * was no such key.
+ */
+ @Override
+ public V put(K key, V value) {
+ final int hash = key.hashCode();
+ int index = indexOf(key, hash);
+ if (index >= 0) {
+ index = (index<<1) + 1;
+ final V old = (V)mArray[index];
+ mArray[index] = value;
+ return old;
+ }
+
+ index = ~index;
+ if (mSize >= mHashes.length) {
+ final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
+ : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
+
+ if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
+
+ final int[] ohashes = mHashes;
+ final Object[] oarray = mArray;
+ allocArrays(n);
+
+ if (mHashes.length > 0) {
+ if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
+ System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
+ System.arraycopy(oarray, 0, mArray, 0, oarray.length);
+ }
+
+ freeArrays(ohashes, oarray, mSize);
+ }
+
+ if (index < mSize) {
+ if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
+ + " to " + (index+1));
+ System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
+ System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
+ }
+
+ mHashes[index] = hash;
+ mArray[index<<1] = key;
+ mArray[(index<<1)+1] = value;
+ mSize++;
+ return null;
+ }
+
+ /**
+ * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
+ * @param array The array whose contents are to be retrieved.
+ */
+ public void putAll(ArrayMap<? extends K, ? extends V> array) {
+ final int N = array.mSize;
+ ensureCapacity(mSize + N);
+ for (int i=0; i<N; i++) {
+ put(array.keyAt(i), array.valueAt(i));
+ }
+ }
+
+ /**
+ * Remove an existing key from the array map.
+ * @param key The key of the mapping to remove.
+ * @return Returns the value that was stored under the key, or null if there
+ * was no such key.
+ */
+ @Override
+ public V remove(Object key) {
+ int index = indexOf(key, key.hashCode());
+ if (index >= 0) {
+ return removeAt(index);
+ }
+
+ return null;
+ }
+
+ /**
+ * Remove the key/value mapping at the given index.
+ * @param index The desired index, must be between 0 and {@link #size()}-1.
+ * @return Returns the value that was stored at this index.
+ */
+ public V removeAt(int index) {
+ final V old = (V)mArray[(index << 1) + 1];
+ if (mSize <= 1) {
+ // Now empty.
+ if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
+ freeArrays(mHashes, mArray, mSize);
+ mHashes = SparseArray.EMPTY_INTS;
+ mArray = SparseArray.EMPTY_OBJECTS;
+ mSize = 0;
+ } else {
+ if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
+ // Shrunk enough to reduce size of arrays. We don't allow it to
+ // shrink smaller than (BASE_SIZE*2) to avoid flapping between
+ // that and BASE_SIZE.
+ final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
+
+ if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
+
+ final int[] ohashes = mHashes;
+ final Object[] oarray = mArray;
+ allocArrays(n);
+
+ mSize--;
+ if (index > 0) {
+ if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
+ System.arraycopy(ohashes, 0, mHashes, 0, index);
+ System.arraycopy(oarray, 0, mArray, 0, index << 1);
+ }
+ if (index < mSize) {
+ if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
+ + " to " + index);
+ System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
+ System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
+ (mSize - index) << 1);
+ }
+ } else {
+ mSize--;
+ if (index < mSize) {
+ if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
+ + " to " + index);
+ System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
+ System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
+ (mSize - index) << 1);
+ }
+ mArray[mSize << 1] = null;
+ mArray[(mSize << 1) + 1] = null;
+ }
+ }
+ return old;
+ }
+
+ /**
+ * Return the number of items in this array map.
+ */
+ @Override
+ public int size() {
+ return mSize;
+ }
+
+ // ------------------------------------------------------------------------
+ // Interop with traditional Java containers. Not as efficient as using
+ // specialized collection APIs.
+ // ------------------------------------------------------------------------
+
+ private MapCollections<K, V> getCollection() {
+ if (mCollections == null) {
+ mCollections = new MapCollections<K, V>() {
+ @Override
+ protected int colGetSize() {
+ return mSize;
+ }
+
+ @Override
+ protected Object colGetEntry(int index, int offset) {
+ return mArray[(index<<1) + offset];
+ }
+
+ @Override
+ protected int colIndexOfKey(Object key) {
+ return indexOf(key, key.hashCode());
+ }
+
+ @Override
+ protected int colIndexOfValue(Object value) {
+ return indexOfValue(value);
+ }
+
+ @Override
+ protected Map<K, V> colGetMap() {
+ return ArrayMap.this;
+ }
+
+ @Override
+ protected void colPut(K key, V value) {
+ put(key, value);
+ }
+
+ @Override
+ protected V colSetValue(int index, V value) {
+ return setValueAt(index, value);
+ }
+
+ @Override
+ protected void colRemoveAt(int index) {
+ removeAt(index);
+ }
+
+ @Override
+ protected void colClear() {
+ clear();
+ }
+ };
+ }
+ return mCollections;
+ }
+
+ /**
+ * Determine if the array map contains all of the keys in the given collection.
+ * @param collection The collection whose contents are to be checked against.
+ * @return Returns true if this array map contains a key for every entry
+ * in <var>collection</var>, else returns false.
+ */
+ public boolean containsAll(Collection<?> collection) {
+ return MapCollections.containsAllHelper(this, collection);
+ }
+
+ /**
+ * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
+ * @param map The map whose contents are to be retrieved.
+ */
+ @Override
+ public void putAll(Map<? extends K, ? extends V> map) {
+ ensureCapacity(mSize + map.size());
+ for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Remove all keys in the array map that exist in the given collection.
+ * @param collection The collection whose contents are to be used to remove keys.
+ * @return Returns true if any keys were removed from the array map, else false.
+ */
+ public boolean removeAll(Collection<?> collection) {
+ return MapCollections.removeAllHelper(this, collection);
+ }
+
+ /**
+ * Remove all keys in the array map that do <b>not</b> exist in the given collection.
+ * @param collection The collection whose contents are to be used to determine which
+ * keys to keep.
+ * @return Returns true if any keys were removed from the array map, else false.
+ */
+ public boolean retainAll(Collection<?> collection) {
+ return MapCollections.retainAllHelper(this, collection);
+ }
+
+ /**
+ * Return a {@link java.util.Set} for iterating over and interacting with all mappings
+ * in the array map.
+ *
+ * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
+ * requires generating a number of temporary objects.</p>
+ *
+ * <p><b>Note:</b></p> the semantics of this
+ * Set are subtly different than that of a {@link java.util.HashMap}: most important,
+ * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
+ * object that exists for the entire iterator, so you can <b>not</b> hold on to it
+ * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
+ */
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ return getCollection().getEntrySet();
+ }
+
+ /**
+ * Return a {@link java.util.Set} for iterating over and interacting with all keys
+ * in the array map.
+ *
+ * <p><b>Note:</b> this is a fair inefficient way to access the array contents, it
+ * requires generating a number of temporary objects.</p>
+ */
+ @Override
+ public Set<K> keySet() {
+ return getCollection().getKeySet();
+ }
+
+ /**
+ * Return a {@link java.util.Collection} for iterating over and interacting with all values
+ * in the array map.
+ *
+ * <p><b>Note:</b> this is a fair inefficient way to access the array contents, it
+ * requires generating a number of temporary objects.</p>
+ */
+ @Override
+ public Collection<V> values() {
+ return getCollection().getValues();
+ }
+}
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index 630e5f3..660b743 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -41,13 +41,19 @@ public class LongSparseArray<E> implements Cloneable {
/**
* Creates a new LongSparseArray containing no mappings that will not
* require any additional memory allocation to store the specified
- * number of mappings.
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
*/
public LongSparseArray(int initialCapacity) {
- initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
-
- mKeys = new long[initialCapacity];
- mValues = new Object[initialCapacity];
+ if (initialCapacity == 0) {
+ mKeys = SparseLongArray.EMPTY_LONGS;
+ mValues = SparseArray.EMPTY_OBJECTS;
+ } else {
+ initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
+ mKeys = new long[initialCapacity];
+ mValues = new Object[initialCapacity];
+ }
mSize = 0;
}
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
index 34b6126..503295c 100644
--- a/core/java/android/util/LongSparseLongArray.java
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -42,13 +42,19 @@ public class LongSparseLongArray implements Cloneable {
/**
* Creates a new SparseLongArray containing no mappings that will not
* require any additional memory allocation to store the specified
- * number of mappings.
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
*/
public LongSparseLongArray(int initialCapacity) {
- initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
-
- mKeys = new long[initialCapacity];
- mValues = new long[initialCapacity];
+ if (initialCapacity == 0) {
+ mKeys = SparseLongArray.EMPTY_LONGS;
+ mValues = SparseLongArray.EMPTY_LONGS;
+ } else {
+ initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
+ mKeys = new long[initialCapacity];
+ mValues = new long[initialCapacity];
+ }
mSize = 0;
}
diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java
new file mode 100644
index 0000000..f29fb65
--- /dev/null
+++ b/core/java/android/util/MapCollections.java
@@ -0,0 +1,496 @@
+/*
+ * 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.util;
+
+import libcore.util.Objects;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Helper for writing standard Java collection interfaces to a data
+ * structure like {@link ArrayMap}.
+ * @hide
+ */
+abstract class MapCollections<K, V> {
+ EntrySet mEntrySet;
+ KeySet mKeySet;
+ ValuesCollection mValues;
+
+ final class ArrayIterator<T> implements Iterator<T> {
+ final int mOffset;
+ int mSize;
+ int mIndex;
+ boolean mCanRemove = false;
+
+ ArrayIterator(int offset) {
+ mOffset = offset;
+ mSize = colGetSize();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mIndex < mSize;
+ }
+
+ @Override
+ public T next() {
+ Object res = colGetEntry(mIndex, mOffset);
+ mIndex++;
+ mCanRemove = true;
+ return (T)res;
+ }
+
+ @Override
+ public void remove() {
+ if (!mCanRemove) {
+ throw new IllegalStateException();
+ }
+ mIndex--;
+ mSize--;
+ mCanRemove = false;
+ colRemoveAt(mIndex);
+ }
+ }
+
+ final class MapIterator implements Iterator<Map.Entry<K, V>>, Map.Entry<K, V> {
+ int mEnd;
+ int mIndex;
+ boolean mEntryValid = false;
+
+ MapIterator() {
+ mEnd = colGetSize() - 1;
+ mIndex = -1;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mIndex < mEnd;
+ }
+
+ @Override
+ public Map.Entry<K, V> next() {
+ mIndex++;
+ mEntryValid = true;
+ return this;
+ }
+
+ @Override
+ public void remove() {
+ if (!mEntryValid) {
+ throw new IllegalStateException();
+ }
+ mIndex--;
+ mEnd--;
+ mEntryValid = false;
+ colRemoveAt(mIndex);
+ }
+
+ @Override
+ public K getKey() {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ return (K)colGetEntry(mIndex, 0);
+ }
+
+ @Override
+ public V getValue() {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ return (V)colGetEntry(mIndex, 1);
+ }
+
+ @Override
+ public V setValue(V object) {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ return colSetValue(mIndex, object);
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+ Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+ return Objects.equal(e.getKey(), colGetEntry(mIndex, 0))
+ && Objects.equal(e.getValue(), colGetEntry(mIndex, 1));
+ }
+
+ @Override
+ public final int hashCode() {
+ if (!mEntryValid) {
+ throw new IllegalStateException(
+ "This container does not support retaining Map.Entry objects");
+ }
+ final Object key = colGetEntry(mIndex, 0);
+ final Object value = colGetEntry(mIndex, 1);
+ return (key == null ? 0 : key.hashCode()) ^
+ (value == null ? 0 : value.hashCode());
+ }
+
+ @Override
+ public final String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
+
+ final class EntrySet implements Set<Map.Entry<K, V>> {
+ @Override
+ public boolean add(Map.Entry<K, V> object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends Map.Entry<K, V>> collection) {
+ int oldSize = colGetSize();
+ for (Map.Entry<K, V> entry : collection) {
+ colPut(entry.getKey(), entry.getValue());
+ }
+ return oldSize != colGetSize();
+ }
+
+ @Override
+ public void clear() {
+ colClear();
+ }
+
+ @Override
+ public boolean contains(Object object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return colGetSize() == 0;
+ }
+
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return new MapIterator();
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ return colGetSize();
+ }
+
+ @Override
+ public Object[] toArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] array) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ final class KeySet implements Set<K> {
+
+ @Override
+ public boolean add(K object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends K> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ colClear();
+ }
+
+ @Override
+ public boolean contains(Object object) {
+ return colIndexOfKey(object) >= 0;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> collection) {
+ return removeAllHelper(colGetMap(), collection);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return colGetSize() == 0;
+ }
+
+ @Override
+ public Iterator<K> iterator() {
+ return new ArrayIterator<K>(0);
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ int index = colIndexOfKey(object);
+ if (index >= 0) {
+ colRemoveAt(index);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ return removeAllHelper(colGetMap(), collection);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ return retainAllHelper(colGetMap(), collection);
+ }
+
+ @Override
+ public int size() {
+ return colGetSize();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return toArrayHelper(1);
+ }
+
+ @Override
+ public <T> T[] toArray(T[] array) {
+ return toArrayHelper(array, 1);
+ }
+ };
+
+ final class ValuesCollection implements Collection<V> {
+
+ @Override
+ public boolean add(V object) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends V> collection) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ colClear();
+ }
+
+ @Override
+ public boolean contains(Object object) {
+ return colIndexOfValue(object) >= 0;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> collection) {
+ Iterator<?> it = collection.iterator();
+ while (it.hasNext()) {
+ if (!contains(it.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return colGetSize() == 0;
+ }
+
+ @Override
+ public Iterator<V> iterator() {
+ return new ArrayIterator<V>(1);
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ int index = colIndexOfValue(object);
+ if (index >= 0) {
+ colRemoveAt(index);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ int N = colGetSize();
+ boolean changed = false;
+ for (int i=0; i<N; i++) {
+ Object cur = colGetEntry(i, 1);
+ if (collection.contains(cur)) {
+ colRemoveAt(i);
+ i--;
+ N--;
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ int N = colGetSize();
+ boolean changed = false;
+ for (int i=0; i<N; i++) {
+ Object cur = colGetEntry(i, 1);
+ if (!collection.contains(cur)) {
+ colRemoveAt(i);
+ i--;
+ N--;
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public int size() {
+ return colGetSize();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return toArrayHelper(1);
+ }
+
+ @Override
+ public <T> T[] toArray(T[] array) {
+ return toArrayHelper(array, 1);
+ }
+ };
+
+ public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) {
+ Iterator<?> it = collection.iterator();
+ while (it.hasNext()) {
+ if (!map.containsKey(it.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) {
+ int oldSize = map.size();
+ Iterator<?> it = collection.iterator();
+ while (it.hasNext()) {
+ map.remove(it.next());
+ }
+ return oldSize != map.size();
+ }
+
+ public static <K, V> boolean retainAllHelper(Map<K, V> map, Collection<?> collection) {
+ int oldSize = map.size();
+ Iterator<K> it = map.keySet().iterator();
+ while (it.hasNext()) {
+ if (!collection.contains(it.next())) {
+ it.remove();
+ }
+ }
+ return oldSize != map.size();
+ }
+
+
+ public Object[] toArrayHelper(int offset) {
+ final int N = colGetSize();
+ Object[] result = new Object[N];
+ for (int i=0; i<N; i++) {
+ result[i] = colGetEntry(i, offset);
+ }
+ return result;
+ }
+
+ public <T> T[] toArrayHelper(T[] array, int offset) {
+ final int N = colGetSize();
+ if (array.length < N) {
+ @SuppressWarnings("unchecked") T[] newArray
+ = (T[]) Array.newInstance(array.getClass().getComponentType(), N);
+ array = newArray;
+ }
+ for (int i=0; i<N; i++) {
+ array[i] = (T)colGetEntry(i, offset);
+ }
+ if (array.length > N) {
+ array[N] = null;
+ }
+ return array;
+ }
+
+ public Set<Map.Entry<K, V>> getEntrySet() {
+ if (mEntrySet == null) {
+ mEntrySet = new EntrySet();
+ }
+ return mEntrySet;
+ }
+
+ public Set<K> getKeySet() {
+ if (mKeySet == null) {
+ mKeySet = new KeySet();
+ }
+ return mKeySet;
+ }
+
+ public Collection<V> getValues() {
+ if (mValues == null) {
+ mValues = new ValuesCollection();
+ }
+ return mValues;
+ }
+
+ protected abstract int colGetSize();
+ protected abstract Object colGetEntry(int index, int offset);
+ protected abstract int colIndexOfKey(Object key);
+ protected abstract int colIndexOfValue(Object key);
+ protected abstract Map<K, V> colGetMap();
+ protected abstract void colPut(K key, V value);
+ protected abstract V colSetValue(int index, V value);
+ protected abstract void colRemoveAt(int index);
+ protected abstract void colClear();
+}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 7e8fee5..001fc5b 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -25,6 +25,8 @@ import com.android.internal.util.ArrayUtils;
*/
public class SparseArray<E> implements Cloneable {
private static final Object DELETED = new Object();
+ static final int[] EMPTY_INTS = new int[0];
+ static final Object[] EMPTY_OBJECTS = new Object[0];
private boolean mGarbage = false;
private int[] mKeys;
@@ -41,13 +43,19 @@ public class SparseArray<E> implements Cloneable {
/**
* Creates a new SparseArray containing no mappings that will not
* require any additional memory allocation to store the specified
- * number of mappings.
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
*/
public SparseArray(int initialCapacity) {
- initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
-
- mKeys = new int[initialCapacity];
- mValues = new Object[initialCapacity];
+ if (initialCapacity == 0) {
+ mKeys = EMPTY_INTS;
+ mValues = EMPTY_OBJECTS;
+ } else {
+ initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+ mKeys = new int[initialCapacity];
+ mValues = new Object[initialCapacity];
+ }
mSize = 0;
}
@@ -79,7 +87,7 @@ public class SparseArray<E> implements Cloneable {
*/
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = binarySearch(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
@@ -92,7 +100,7 @@ public class SparseArray<E> implements Cloneable {
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
@@ -153,7 +161,7 @@ public class SparseArray<E> implements Cloneable {
* was one.
*/
public void put(int key, E value) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = binarySearch(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
@@ -170,7 +178,7 @@ public class SparseArray<E> implements Cloneable {
gc();
// Search again because indices may have changed.
- i = ~binarySearch(mKeys, 0, mSize, key);
+ i = ~binarySearch(mKeys, mSize, key);
}
if (mSize >= mKeys.length) {
@@ -261,7 +269,7 @@ public class SparseArray<E> implements Cloneable {
gc();
}
- return binarySearch(mKeys, 0, mSize, key);
+ return binarySearch(mKeys, mSize, key);
}
/**
@@ -335,23 +343,23 @@ public class SparseArray<E> implements Cloneable {
mSize = pos + 1;
}
- private static int binarySearch(int[] a, int start, int len, int key) {
- int high = start + len, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (a[guess] < key)
- low = guess;
- else
- high = guess;
+ // This is Arrays.binarySearch(), but doesn't do any argument validation.
+ static int binarySearch(int[] array, int size, int value) {
+ int lo = 0;
+ int hi = size - 1;
+
+ while (lo <= hi) {
+ int mid = (lo + hi) >>> 1;
+ int midVal = array[mid];
+
+ if (midVal < value) {
+ lo = mid + 1;
+ } else if (midVal > value) {
+ hi = mid - 1;
+ } else {
+ return mid; // value found
+ }
}
-
- if (high == start + len)
- return ~(start + len);
- else if (a[high] == key)
- return high;
- else
- return ~high;
+ return ~lo; // value not present
}
}
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 76c47c6..73e3629 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -25,6 +25,8 @@ import com.android.internal.util.ArrayUtils;
* than using a HashMap to map Integers to Booleans.
*/
public class SparseBooleanArray implements Cloneable {
+ static final boolean[] EMPTY_BOOLEANS = new boolean[0];
+
/**
* Creates a new SparseBooleanArray containing no mappings.
*/
@@ -35,13 +37,19 @@ public class SparseBooleanArray implements Cloneable {
/**
* Creates a new SparseBooleanArray containing no mappings that will not
* require any additional memory allocation to store the specified
- * number of mappings.
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
*/
public SparseBooleanArray(int initialCapacity) {
- initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
-
- mKeys = new int[initialCapacity];
- mValues = new boolean[initialCapacity];
+ if (initialCapacity == 0) {
+ mKeys = SparseArray.EMPTY_INTS;
+ mValues = EMPTY_BOOLEANS;
+ } else {
+ initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+ mKeys = new int[initialCapacity];
+ mValues = new boolean[initialCapacity];
+ }
mSize = 0;
}
@@ -71,7 +79,7 @@ public class SparseBooleanArray implements Cloneable {
* if no such mapping has been made.
*/
public boolean get(int key, boolean valueIfKeyNotFound) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i < 0) {
return valueIfKeyNotFound;
@@ -84,7 +92,7 @@ public class SparseBooleanArray implements Cloneable {
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i >= 0) {
System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
@@ -99,7 +107,7 @@ public class SparseBooleanArray implements Cloneable {
* was one.
*/
public void put(int key, boolean value) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
@@ -164,7 +172,7 @@ public class SparseBooleanArray implements Cloneable {
* key is not mapped.
*/
public int indexOfKey(int key) {
- return binarySearch(mKeys, 0, mSize, key);
+ return SparseArray.binarySearch(mKeys, mSize, key);
}
/**
@@ -220,26 +228,6 @@ public class SparseBooleanArray implements Cloneable {
mSize = pos + 1;
}
- private static int binarySearch(int[] a, int start, int len, int key) {
- int high = start + len, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (a[guess] < key)
- low = guess;
- else
- high = guess;
- }
-
- if (high == start + len)
- return ~(start + len);
- else if (a[high] == key)
- return high;
- else
- return ~high;
- }
-
private int[] mKeys;
private boolean[] mValues;
private int mSize;
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index 8d11177..122f7f5 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -24,7 +24,6 @@ import com.android.internal.util.ArrayUtils;
* than using a HashMap to map Integers to Integers.
*/
public class SparseIntArray implements Cloneable {
-
private int[] mKeys;
private int[] mValues;
private int mSize;
@@ -39,13 +38,19 @@ public class SparseIntArray implements Cloneable {
/**
* Creates a new SparseIntArray containing no mappings that will not
* require any additional memory allocation to store the specified
- * number of mappings.
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
*/
public SparseIntArray(int initialCapacity) {
- initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
-
- mKeys = new int[initialCapacity];
- mValues = new int[initialCapacity];
+ if (initialCapacity == 0) {
+ mKeys = SparseArray.EMPTY_INTS;
+ mValues = SparseArray.EMPTY_INTS;
+ } else {
+ initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+ mKeys = new int[initialCapacity];
+ mValues = new int[initialCapacity];
+ }
mSize = 0;
}
@@ -75,7 +80,7 @@ public class SparseIntArray implements Cloneable {
* if no such mapping has been made.
*/
public int get(int key, int valueIfKeyNotFound) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i < 0) {
return valueIfKeyNotFound;
@@ -88,7 +93,7 @@ public class SparseIntArray implements Cloneable {
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i >= 0) {
removeAt(i);
@@ -110,7 +115,7 @@ public class SparseIntArray implements Cloneable {
* was one.
*/
public void put(int key, int value) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
@@ -175,7 +180,7 @@ public class SparseIntArray implements Cloneable {
* key is not mapped.
*/
public int indexOfKey(int key) {
- return binarySearch(mKeys, 0, mSize, key);
+ return SparseArray.binarySearch(mKeys, mSize, key);
}
/**
@@ -230,24 +235,4 @@ public class SparseIntArray implements Cloneable {
mValues[pos] = value;
mSize = pos + 1;
}
-
- private static int binarySearch(int[] a, int start, int len, int key) {
- int high = start + len, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (a[guess] < key)
- low = guess;
- else
- high = guess;
- }
-
- if (high == start + len)
- return ~(start + len);
- else if (a[high] == key)
- return high;
- else
- return ~high;
- }
}
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 2f7a6fe..c608996 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -24,6 +24,7 @@ import com.android.internal.util.ArrayUtils;
* than using a HashMap to map Integers to Longs.
*/
public class SparseLongArray implements Cloneable {
+ static final long[] EMPTY_LONGS = new long[0];
private int[] mKeys;
private long[] mValues;
@@ -39,13 +40,19 @@ public class SparseLongArray implements Cloneable {
/**
* Creates a new SparseLongArray containing no mappings that will not
* require any additional memory allocation to store the specified
- * number of mappings.
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
*/
public SparseLongArray(int initialCapacity) {
- initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
-
- mKeys = new int[initialCapacity];
- mValues = new long[initialCapacity];
+ if (initialCapacity == 0) {
+ mKeys = SparseArray.EMPTY_INTS;
+ mValues = EMPTY_LONGS;
+ } else {
+ initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
+ mKeys = new int[initialCapacity];
+ mValues = new long[initialCapacity];
+ }
mSize = 0;
}
@@ -75,7 +82,7 @@ public class SparseLongArray implements Cloneable {
* if no such mapping has been made.
*/
public long get(int key, long valueIfKeyNotFound) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i < 0) {
return valueIfKeyNotFound;
@@ -88,7 +95,7 @@ public class SparseLongArray implements Cloneable {
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i >= 0) {
removeAt(i);
@@ -110,7 +117,7 @@ public class SparseLongArray implements Cloneable {
* was one.
*/
public void put(int key, long value) {
- int i = binarySearch(mKeys, 0, mSize, key);
+ int i = SparseArray.binarySearch(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
@@ -164,7 +171,7 @@ public class SparseLongArray implements Cloneable {
* key is not mapped.
*/
public int indexOfKey(int key) {
- return binarySearch(mKeys, 0, mSize, key);
+ return SparseArray.binarySearch(mKeys, mSize, key);
}
/**
@@ -222,24 +229,4 @@ public class SparseLongArray implements Cloneable {
mKeys = nkeys;
mValues = nvalues;
}
-
- private static int binarySearch(int[] a, int start, int len, long key) {
- int high = start + len, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (a[guess] < key)
- low = guess;
- else
- high = guess;
- }
-
- if (high == start + len)
- return ~(start + len);
- else if (a[high] == key)
- return high;
- else
- return ~high;
- }
}
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..c07b187 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.
@@ -386,6 +406,17 @@ public abstract class HardwareRenderer {
private static native void nBeginFrame(int[] size);
/**
+ * Returns the current system time according to the renderer.
+ * This method is used for debugging only and should not be used
+ * as a clock.
+ */
+ static long getSystemTime() {
+ return nGetSystemTime();
+ }
+
+ private static native long nGetSystemTime();
+
+ /**
* Preserves the back buffer of the current surface after a buffer swap.
* Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
* surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
@@ -762,6 +793,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 +849,9 @@ public abstract class HardwareRenderer {
Paint mProfilePaint;
boolean mDebugDirtyRegions;
- boolean mShowOverdraw;
+ int mDebugOverdraw = -1;
+ HardwareLayer mDebugOverdrawLayer;
+ Paint mDebugOverdrawPaint;
final int mGlVersion;
final boolean mTranslucent;
@@ -819,6 +863,8 @@ public abstract class HardwareRenderer {
private final int[] mSurfaceSize = new int[2];
private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable();
+ private long mDrawDelta = Long.MAX_VALUE;
+
GlRenderer(int glVersion, boolean translucent) {
mGlVersion = glVersion;
mTranslucent = translucent;
@@ -826,18 +872,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 +935,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 +957,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 +1024,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 +1032,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 +1047,10 @@ public abstract class HardwareRenderer {
mCanvas.setName(mName);
}
setEnabled(true);
+
+ if (contextCreated) {
+ initAtlas();
+ }
}
return mCanvas != null;
@@ -1010,7 +1070,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 +1103,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 +1244,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 +1257,7 @@ public abstract class HardwareRenderer {
"Could not create an EGL context. eglCreateContext failed with error: " +
GLUtils.getEGLErrorString(sEgl.eglGetError()));
}
+
return context;
}
@@ -1361,6 +1426,7 @@ public abstract class HardwareRenderer {
int saveCount = 0;
int status = DisplayList.STATUS_DONE;
+ long start = getSystemTime();
try {
status = prepareFrame(dirty);
@@ -1380,10 +1446,15 @@ public abstract class HardwareRenderer {
canvas.restoreToCount(saveCount);
view.mRecreateDisplayList = false;
- mFrameCount++;
+ mDrawDelta = getSystemTime() - start;
+
+ if (mDrawDelta > 0) {
+ mFrameCount++;
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
+ debugOverdraw(attachInfo, dirty, canvas, displayList);
+ debugDirtyRegions(dirty, canvas);
+ drawProfileData(attachInfo);
+ }
}
onPostDraw();
@@ -1399,7 +1470,66 @@ 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) {
+ if (mDrawDelta <= 0) {
+ return view.mDisplayList;
+ }
+
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
@@ -1788,7 +1918,31 @@ 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 {
+ if (atlas.isCompatible(android.os.Process.myPpid())) {
+ 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 +2139,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..5f1e238
--- /dev/null
+++ b/core/java/android/view/IAssetAtlas.aidl
@@ -0,0 +1,54 @@
+/**
+ * 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 {
+ /**
+ * Indicates whether the atlas is compatible with the specified
+ * parent process id. If the atlas' ppid does not match, this
+ * method will return false.
+ */
+ boolean isCompatible(int ppid);
+
+ /**
+ * 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/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8ed4a86..ad3082e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -80,8 +80,8 @@ interface IWindowManager
void setEventDispatching(boolean enabled);
void addWindowToken(IBinder token, int type);
void removeWindowToken(IBinder token);
- void addAppToken(int addPos, IApplicationToken token,
- int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked);
+ void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
+ int requestedOrientation, boolean fullscreen, boolean showWhenLocked);
void setAppGroupId(IBinder token, int groupId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
@@ -97,15 +97,12 @@ interface IWindowManager
void executeAppTransition();
void setAppStartingWindow(IBinder token, String pkg, int theme,
in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
- int icon, int windowFlags, IBinder transferFrom, boolean createIfNeeded);
+ int icon, int logo, int windowFlags, IBinder transferFrom, boolean createIfNeeded);
void setAppWillBeHidden(IBinder token);
void setAppVisibility(IBinder token, boolean visible);
void startAppFreezingScreen(IBinder token, int configChanges);
void stopAppFreezingScreen(IBinder token, boolean force);
void removeAppToken(IBinder token);
- void moveAppToken(int index, IBinder token);
- void moveAppTokensToTop(in List<IBinder> tokens);
- void moveAppTokensToBottom(in List<IBinder> tokens);
// Re-evaluate the current orientation from the caller's state.
// If there is a change, the new Configuration is returned and the
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 07a937c..1ecdf30 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -70,6 +70,7 @@ public abstract class InputEvent implements Parcelable {
* Gets the source of the event.
*
* @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown.
+ * @see InputDevice#getSources
*/
public abstract int getSource();
diff --git a/core/java/android/view/InputFilter.java b/core/java/android/view/InputFilter.java
index c25b87b..4aba30c 100644
--- a/core/java/android/view/InputFilter.java
+++ b/core/java/android/view/InputFilter.java
@@ -40,7 +40,7 @@ import android.view.WindowManagerPolicy;
* <li>Input events are then asynchronously delivered to the input filter's
* {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to
* applications as usual. The input filter only receives input events that were
- * generated by input device; the input filter will not receive input events that were
+ * generated by an input device; the input filter will not receive input events that were
* injected into the system by other means, such as by instrumentation.</li>
* <li>The input filter processes and optionally transforms the stream of events. For example,
* it may transform a sequence of motion events representing an accessibility gesture into
@@ -68,7 +68,7 @@ import android.view.WindowManagerPolicy;
* The input filter must take into account the fact that the input events coming from different
* devices or even different sources all consist of distinct streams of input.
* Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify
- * the source of the event and its semantics. There are be multiple sources of keys,
+ * the source of the event and its semantics. There may be multiple sources of keys,
* touches and other input: they must be kept separate.
* </p>
* <h3>Policy flags</h3>
@@ -88,7 +88,7 @@ import android.view.WindowManagerPolicy;
* The input filter should clear its internal state about the gesture and then send key or
* motion events to the dispatcher to cancel any keys or pointers that are down.
* </p><p>
- * Corollary: Events that set sent to the dispatcher should usually include the
+ * Corollary: Events that get sent to the dispatcher should usually include the
* {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped!
* </p><p>
* It may be prudent to disable automatic key repeating for synthetic key events
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ae4005b..e0786f7 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -71,8 +71,8 @@ public class Surface implements Parcelable {
// Guarded state.
final Object mLock = new Object(); // protects the native state
private String mName;
- int mNativeSurface; // package scope only for SurfaceControl access
- private int mGenerationId; // incremented each time mNativeSurface changes
+ int mNativeObject; // package scope only for SurfaceControl access
+ 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
@@ -158,8 +158,8 @@ public class Surface implements Parcelable {
*/
public void release() {
synchronized (mLock) {
- if (mNativeSurface != 0) {
- nativeRelease(mNativeSurface);
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
setNativeObjectLocked(0);
}
}
@@ -183,8 +183,8 @@ public class Surface implements Parcelable {
*/
public boolean isValid() {
synchronized (mLock) {
- if (mNativeSurface == 0) return false;
- return nativeIsValid(mNativeSurface);
+ if (mNativeObject == 0) return false;
+ return nativeIsValid(mNativeObject);
}
}
@@ -210,7 +210,7 @@ public class Surface implements Parcelable {
public boolean isConsumerRunningBehind() {
synchronized (mLock) {
checkNotReleasedLocked();
- return nativeIsConsumerRunningBehind(mNativeSurface);
+ return nativeIsConsumerRunningBehind(mNativeObject);
}
}
@@ -233,7 +233,7 @@ public class Surface implements Parcelable {
throws OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
- nativeLockCanvas(mNativeSurface, mCanvas, inOutDirty);
+ nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
@@ -252,7 +252,7 @@ public class Surface implements Parcelable {
synchronized (mLock) {
checkNotReleasedLocked();
- nativeUnlockCanvasAndPost(mNativeSurface, canvas);
+ nativeUnlockCanvasAndPost(mNativeObject, canvas);
}
}
@@ -298,8 +298,8 @@ public class Surface implements Parcelable {
int newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
synchronized (mLock) {
- if (mNativeSurface != 0) {
- nativeRelease(mNativeSurface);
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
}
setNativeObjectLocked(newNativeObject);
}
@@ -319,13 +319,13 @@ public class Surface implements Parcelable {
if (other != this) {
final int newPtr;
synchronized (other.mLock) {
- newPtr = other.mNativeSurface;
+ newPtr = other.mNativeObject;
other.setNativeObjectLocked(0);
}
synchronized (mLock) {
- if (mNativeSurface != 0) {
- nativeRelease(mNativeSurface);
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
}
setNativeObjectLocked(newPtr);
}
@@ -344,7 +344,7 @@ public class Surface implements Parcelable {
synchronized (mLock) {
mName = source.readString();
- setNativeObjectLocked(nativeReadFromParcel(mNativeSurface, source));
+ setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
}
}
@@ -355,7 +355,7 @@ public class Surface implements Parcelable {
}
synchronized (mLock) {
dest.writeString(mName);
- nativeWriteToParcel(mNativeSurface, dest);
+ nativeWriteToParcel(mNativeObject, dest);
}
if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
release();
@@ -370,19 +370,19 @@ public class Surface implements Parcelable {
}
private void setNativeObjectLocked(int ptr) {
- if (mNativeSurface != ptr) {
- if (mNativeSurface == 0 && ptr != 0) {
+ if (mNativeObject != ptr) {
+ if (mNativeObject == 0 && ptr != 0) {
mCloseGuard.open("release");
- } else if (mNativeSurface != 0 && ptr == 0) {
+ } else if (mNativeObject != 0 && ptr == 0) {
mCloseGuard.close();
}
- mNativeSurface = ptr;
+ mNativeObject = ptr;
mGenerationId += 1;
}
}
private void checkNotReleasedLocked() {
- if (mNativeSurface == 0) {
+ if (mNativeObject == 0) {
throw new IllegalStateException("Surface has already been released.");
}
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c6da84f..6b530ef 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -499,7 +499,7 @@ public class SurfaceControl {
if (surface != null) {
synchronized (surface.mLock) {
- nativeSetDisplaySurface(displayToken, surface.mNativeSurface);
+ nativeSetDisplaySurface(displayToken, surface.mNativeObject);
}
} else {
nativeSetDisplaySurface(displayToken, 0);
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/TextureView.java b/core/java/android/view/TextureView.java
index 5c3934d..f0acba1 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -648,13 +648,19 @@ public class TextureView extends View {
* rectangle. Every pixel within that rectangle must be written; however
* pixels outside the dirty rectangle will be preserved by the next call
* to lockCanvas().
+ *
+ * This method can return null if the underlying surface texture is not
+ * available (see {@link #isAvailable()} or if the surface texture is
+ * already connected to an image producer (for instance: the camera,
+ * OpenGL, a media player, etc.)
*
* @param dirty Area of the surface that will be modified.
* @return A Canvas used to draw into the surface.
*
* @see #lockCanvas()
- * @see #unlockCanvasAndPost(android.graphics.Canvas)
+ * @see #unlockCanvasAndPost(android.graphics.Canvas)
+ * @see #isAvailable()
*/
public Canvas lockCanvas(Rect dirty) {
if (!isAvailable()) return null;
@@ -664,7 +670,9 @@ public class TextureView extends View {
}
synchronized (mNativeWindowLock) {
- nLockCanvas(mNativeWindow, mCanvas, dirty);
+ if (!nLockCanvas(mNativeWindow, mCanvas, dirty)) {
+ return null;
+ }
}
mSaveCount = mCanvas.save();
@@ -803,6 +811,6 @@ public class TextureView extends View {
private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture,
int width, int height);
- private static native void nLockCanvas(int nativeWindow, Canvas canvas, Rect dirty);
+ private static native boolean nLockCanvas(int nativeWindow, Canvas canvas, Rect dirty);
private static native void nUnlockCanvasAndPost(int nativeWindow, Canvas canvas);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 50638aa..6f88bb2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -73,6 +73,7 @@ import android.view.animation.Transformation;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.transition.Scene;
import android.widget.ScrollBarDrawable;
import static android.os.Build.VERSION_CODES.*;
@@ -1572,6 +1573,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
protected Object mTag;
+ private Scene mCurrentScene = null;
+
// for mPrivateFlags:
/** {@hide} */
static final int PFLAG_WANTS_FOCUS = 0x00000001;
@@ -2341,7 +2344,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* allows it to avoid artifacts when switching in and out of that mode, at
* the expense that some of its user interface may be covered by screen
* decorations when they are shown. You can perform layout of your inner
- * UI elements to account for the navagation system UI through the
+ * UI elements to account for the navigation system UI through the
* {@link #fitSystemWindows(Rect)} method.
*/
public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200;
@@ -2359,6 +2362,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
/**
+ * Flag for {@link #setSystemUiVisibility(int)}: View would like to receive touch events
+ * when hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the
+ * navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} instead of having the system
+ * clear these flags upon interaction. The system may compensate by temporarily overlaying
+ * transparent system ui while also delivering the event.
+ */
+ public static final int SYSTEM_UI_FLAG_ALLOW_OVERLAY = 0x00000800;
+
+ /**
* @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead.
*/
public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE;
@@ -2479,6 +2491,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the status bar should temporarily overlay underlying content
+ * that is otherwise assuming the status bar is hidden. The status bar may
+ * have some degree of transparency while in this temporary overlay mode.
+ */
+ public static final int STATUS_BAR_OVERLAY = 0x04000000;
+
+ /**
+ * @hide
+ *
+ * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+ * out of the public fields to keep the undefined bits out of the developer's way.
+ *
+ * Flag to specify that the navigation bar should temporarily overlay underlying content
+ * that is otherwise assuming the navigation bar is hidden. The navigation bar mayu
+ * have some degree of transparency while in this temporary overlay mode.
+ */
+ public static final int NAVIGATION_BAR_OVERLAY = 0x08000000;
+
+ /**
+ * @hide
*/
public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF;
@@ -8556,7 +8592,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- if ((flags & VISIBILITY_MASK) == VISIBLE) {
+ final int newVisibility = flags & VISIBILITY_MASK;
+ if (newVisibility == VISIBLE) {
if ((changed & VISIBILITY_MASK) != 0) {
/*
* If this view is becoming visible, invalidate it in case it changed while
@@ -8622,14 +8659,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if ((changed & VISIBILITY_MASK) != 0) {
+ // If the view is invisible, cleanup its display list to free up resources
+ if (newVisibility != VISIBLE) {
+ cleanupDraw();
+ }
+
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onChildVisibilityChanged(this,
- (changed & VISIBILITY_MASK), (flags & VISIBILITY_MASK));
+ (changed & VISIBILITY_MASK), newVisibility);
((View) mParent).invalidate(true);
} else if (mParent != null) {
mParent.invalidateChild(this, null);
}
- dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
+ dispatchVisibilityChanged(this, newVisibility);
}
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
@@ -12009,6 +12051,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
destroyLayer(false);
+ cleanupDraw();
+
+ mCurrentAnimation = null;
+ mCurrentScene = null;
+
+ resetAccessibilityStateChanged();
+ }
+
+ private void cleanupDraw() {
if (mAttachInfo != null) {
if (mDisplayList != null) {
mDisplayList.markDirty();
@@ -12019,10 +12070,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Should never happen
clearDisplayList();
}
+ }
- mCurrentAnimation = null;
-
- resetAccessibilityStateChanged();
+ void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
}
/**
@@ -12640,6 +12690,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
protected void destroyHardwareResources() {
+ clearDisplayList();
destroyLayer(true);
}
@@ -15637,7 +15688,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private void setKeyedTag(int key, Object tag) {
if (mKeyedTags == null) {
- mKeyedTags = new SparseArray<Object>();
+ mKeyedTags = new SparseArray<Object>(2);
}
mKeyedTags.put(key, tag);
@@ -17742,6 +17793,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Set the current Scene that this view is in. The current scene is set only
+ * on the root view of a scene, not for every view in that hierarchy. This
+ * information is used by Scene to determine whether there is a previous
+ * scene which should be exited before the new scene is entered.
+ *
+ * @param scene The new scene being set on the view
+ *
+ * @hide
+ */
+ public void setCurrentScene(Scene scene) {
+ mCurrentScene = scene;
+ }
+
+ /**
+ * Gets the current {@link Scene} set on this view. A scene is set on a view
+ * only if that view is the scene root.
+ *
+ * @return The current Scene set on this view. A value of null indicates that
+ * no Scene is current set.
+ */
+ public Scene getCurrentScene() {
+ return mCurrentScene;
+ }
+
+ /**
* Interface definition for a callback to be invoked when a hardware key event is
* dispatched to this view. The callback will be invoked before the key event is
* given to the view. This is only useful for hardware keyboards; a software input
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 426c9d4..b0fbe84 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -204,7 +204,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*/
- private int mLayoutMode = DEFAULT_LAYOUT_MODE;
+ private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
/**
* NOTE: If you change the flags below make sure to reflect the changes
@@ -345,6 +345,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW = 0x400000;
/**
+ * When true, indicates that a layoutMode has been explicitly set, either with
+ * an explicit call to {@link #setLayoutMode(int)} in code or from an XML resource.
+ * This distinguishes the situation in which a layout mode was inherited from
+ * one of the ViewGroup's ancestors and cached locally.
+ */
+ private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
+
+ /**
* Indicates which types of drawing caches are to be kept in memory.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -373,6 +381,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Layout Modes
+ private static final int LAYOUT_MODE_UNDEFINED = -1;
+
/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
@@ -389,7 +399,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
/** @hide */
- public static int DEFAULT_LAYOUT_MODE = LAYOUT_MODE_CLIP_BOUNDS;
+ public static int LAYOUT_MODE_DEFAULT = LAYOUT_MODE_CLIP_BOUNDS;
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
@@ -531,7 +541,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
break;
case R.styleable.ViewGroup_layoutMode:
- setLayoutMode(a.getInt(attr, DEFAULT_LAYOUT_MODE));
+ setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
break;
}
}
@@ -3448,6 +3458,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ private void clearCachedLayoutMode() {
+ if (!getBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
+ mLayoutMode = LAYOUT_MODE_UNDEFINED;
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ clearCachedLayoutMode();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ clearCachedLayoutMode();
+ }
+
/**
* Adds a view during layout. This is useful if in your onLayout() method,
* you need to add more views (as does the list view for example).
@@ -4751,6 +4779,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
}
+ private boolean getBooleanFlag(int flag) {
+ return (mGroupFlags & flag) == flag;
+ }
+
private void setBooleanFlag(int flag, boolean value) {
if (value) {
mGroupFlags |= flag;
@@ -4794,24 +4826,63 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
}
+ private void setLayoutMode(int layoutMode, boolean explicitly) {
+ mLayoutMode = layoutMode;
+ setBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET, explicitly);
+ }
+
/**
- * Returns the basis of alignment during layout operations on this view group:
+ * Recursively traverse the view hierarchy, resetting the layoutMode of any
+ * descendants that had inherited a different layoutMode from a previous parent.
+ * Recursion terminates when a descendant's mode is:
+ * <ul>
+ * <li>Undefined</li>
+ * <li>The same as the root node's</li>
+ * <li>A mode that had been explicitly set</li>
+ * <ul/>
+ * The first two clauses are optimizations.
+ * @param layoutModeOfRoot
+ */
+ @Override
+ void invalidateInheritedLayoutMode(int layoutModeOfRoot) {
+ if (mLayoutMode == LAYOUT_MODE_UNDEFINED ||
+ mLayoutMode == layoutModeOfRoot ||
+ getBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) {
+ return;
+ }
+ setLayoutMode(LAYOUT_MODE_UNDEFINED, false);
+
+ // apply recursively
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ getChildAt(i).invalidateInheritedLayoutMode(layoutModeOfRoot);
+ }
+ }
+
+ /**
+ * Returns the basis of alignment during layout operations on this ViewGroup:
* either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
+ * <p>
+ * If no layoutMode was explicitly set, either programmatically or in an XML resource,
+ * the method returns the layoutMode of the view's parent ViewGroup if such a parent exists,
+ * otherwise the method returns a default value of {@link #LAYOUT_MODE_CLIP_BOUNDS}.
*
* @return the layout mode to use during layout operations
*
* @see #setLayoutMode(int)
*/
public int getLayoutMode() {
+ if (mLayoutMode == LAYOUT_MODE_UNDEFINED) {
+ int inheritedLayoutMode = (mParent instanceof ViewGroup) ?
+ ((ViewGroup) mParent).getLayoutMode() : LAYOUT_MODE_DEFAULT;
+ setLayoutMode(inheritedLayoutMode, false);
+ }
return mLayoutMode;
}
/**
- * Sets the basis of alignment during the layout of this view group.
+ * Sets the basis of alignment during the layout of this ViewGroup.
* Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or
* {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
- * <p>
- * The default is {@link #LAYOUT_MODE_CLIP_BOUNDS}.
*
* @param layoutMode the layout mode to use during layout operations
*
@@ -4819,7 +4890,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public void setLayoutMode(int layoutMode) {
if (mLayoutMode != layoutMode) {
- mLayoutMode = layoutMode;
+ invalidateInheritedLayoutMode(layoutMode);
+ setLayoutMode(layoutMode, layoutMode != LAYOUT_MODE_UNDEFINED);
requestLayout();
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bbf5ae9..bcc58a2 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,13 +107,12 @@ 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.
*/
- private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering";
+ private static final String PROPERTY_PROFILE_RENDERING = "viewroot.profile_rendering";
+ private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media";
/**
* Maximum time we allow the user to roll the trackball enough to generate
@@ -130,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;
@@ -286,6 +280,8 @@ public final class ViewRootImpl implements ViewParent,
private Choreographer.FrameCallback mRenderProfiler;
private boolean mRenderProfilingEnabled;
+ private boolean mMediaDisabled;
+
// Variables to track frames per second, enabled via DEBUG_FPS flag
private long mFpsStartTime = -1;
private long mFpsPrevTime = -1;
@@ -373,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) {
@@ -479,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;
@@ -687,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;
@@ -727,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);
}
@@ -5057,6 +5019,10 @@ public final class ViewRootImpl implements ViewParent,
public void playSoundEffect(int effectId) {
checkThread();
+ if (mMediaDisabled) {
+ return;
+ }
+
try {
final AudioManager audioManager = getAudioManager();
@@ -5202,6 +5168,9 @@ public final class ViewRootImpl implements ViewParent,
mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
profileRendering(mAttachInfo.mHasWindowFocus);
+ // Media (used by sound effects)
+ mMediaDisabled = SystemProperties.getBoolean(PROPERTY_MEDIA_DISABLED, false);
+
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 06974d3..39d48a7 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1256,4 +1256,38 @@ public abstract class Window {
* @param mask Flags specifying which options should be modified. Others will remain unchanged.
*/
public void setUiOptions(int uiOptions, int mask) { }
+
+ /**
+ * Set the primary icon for this window.
+ *
+ * @param resId resource ID of a drawable to set
+ */
+ public void setIcon(int resId) { }
+
+ /**
+ * Set the default icon for this window.
+ * This will be overridden by any other icon set operation which could come from the
+ * theme or another explicit set.
+ *
+ * @hide
+ */
+ public void setDefaultIcon(int resId) { }
+
+ /**
+ * Set the logo for this window. A logo is often shown in place of an
+ * {@link #setIcon(int) icon} but is generally wider and communicates window title information
+ * as well.
+ *
+ * @param resId resource ID of a drawable to set
+ */
+ public void setLogo(int resId) { }
+
+ /**
+ * Set the default logo for this window.
+ * This will be overridden by any other logo set operation which could come from the
+ * theme or another explicit set.
+ *
+ * @hide
+ */
+ public void setDefaultLogo(int resId) { }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 541c503..5144889 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -527,6 +527,14 @@ public interface WindowManager extends ViewManager {
*/
public static final int TYPE_RECENTS_OVERLAY = FIRST_SYSTEM_WINDOW+28;
+
+ /**
+ * Window type: keyguard scrim window. Shows if keyguard needs to be restarted.
+ * In multiuser systems shows on all users' windows.
+ * @hide
+ */
+ public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29;
+
/**
* End of types of system windows.
*/
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index c0044b6..6291e62 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -420,6 +420,11 @@ public interface WindowManagerPolicy {
public void shutdown(boolean confirm);
public void rebootSafeMode(boolean confirm);
+
+ /**
+ * Return the window manager lock needed to correctly call "Lw" methods.
+ */
+ public Object getWindowManagerLock();
}
/** Window has been added to the screen. */
@@ -637,7 +642,7 @@ public interface WindowManagerPolicy {
*/
public View addStartingWindow(IBinder appToken, String packageName,
int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel,
- int labelRes, int icon, int windowFlags);
+ int labelRes, int icon, int logo, int windowFlags);
/**
* Called when the first window of an application has been displayed, while
@@ -816,6 +821,14 @@ public interface WindowManagerPolicy {
public int getSystemDecorRectLw(Rect systemRect);
/**
+ * Return the rectangle of the screen that is available for applications to run in.
+ * This will be called immediately after {@link #beginLayoutLw}.
+ *
+ * @param r The rectangle to be filled with the boundaries available to applications.
+ */
+ public void getContentRectLw(Rect r);
+
+ /**
* Called for each window attached to the window manager as layout is
* proceeding. The implementation of this function must take care of
* setting the window's frame, either here or in finishLayout().
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 732699b..04ce7e2 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -16,14 +16,17 @@
package android.view.accessibility;
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -132,29 +135,6 @@ public final class AccessibilityManager {
}
/**
- * Creates the singleton AccessibilityManager to be shared across users. This
- * has to be called before the local AccessibilityManager is created to ensure
- * it registers itself in the system correctly.
- * <p>
- * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or
- * INTERACT_ACROSS_USERS permission.
- * </p>
- * @param context Context in which this manager operates.
- * @throws IllegalStateException if not called before the local
- * AccessibilityManager is instantiated.
- *
- * @hide
- */
- public static void createAsSharedAcrossUsers(Context context) {
- synchronized (sInstanceSync) {
- if (sInstance != null) {
- throw new IllegalStateException("AccessibilityManager already created.");
- }
- createSingletonInstance(context, UserHandle.USER_CURRENT);
- }
- }
-
- /**
* Get an AccessibilityManager instance (create one if necessary).
*
* @param context Context in which this manager operates.
@@ -164,25 +144,27 @@ public final class AccessibilityManager {
public static AccessibilityManager getInstance(Context context) {
synchronized (sInstanceSync) {
if (sInstance == null) {
- createSingletonInstance(context, UserHandle.myUserId());
+ final int userId;
+ if (Binder.getCallingUid() == Process.SYSTEM_UID
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_GRANTED
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ userId = UserHandle.myUserId();
+ }
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
+ sInstance = new AccessibilityManager(context, service, userId);
}
}
return sInstance;
}
/**
- * Creates the singleton instance.
- *
- * @param context Context in which this manager operates.
- * @param userId The user id under which to operate.
- */
- private static void createSingletonInstance(Context context, int userId) {
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
- sInstance = new AccessibilityManager(context, service, userId);
- }
-
- /**
* Create an instance.
*
* @param context A {@link Context}.
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index d6b973e..f730cf7 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -269,8 +269,9 @@ public class BaseInputConnection implements InputConnection {
if (content != null) {
beginBatchEdit();
removeComposingSpans(content);
- endBatchEdit();
+ // Note: sendCurrentText does nothing unless mDummyMode is set
sendCurrentText();
+ endBatchEdit();
}
return true;
}
@@ -467,8 +468,9 @@ public class BaseInputConnection implements InputConnection {
content.setSpan(COMPOSING, a, b,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
- endBatchEdit();
+ // Note: sendCurrentText does nothing unless mDummyMode is set
sendCurrentText();
+ endBatchEdit();
}
return true;
}
diff --git a/core/java/android/view/transition/AutoTransition.java b/core/java/android/view/transition/AutoTransition.java
new file mode 100644
index 0000000..d94cf2c
--- /dev/null
+++ b/core/java/android/view/transition/AutoTransition.java
@@ -0,0 +1,34 @@
+/*
+ * 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.transition;
+
+/**
+ * Utility class for creating a default transition that automatically fades,
+ * moves, and resizes views during a scene change.
+ */
+public class AutoTransition extends TransitionGroup {
+
+ /**
+ * Constructs an AutoTransition object, which is a TransitionGroup which
+ * first fades out disappearing targets, then moves and resizes existing
+ * targets, and finally fades in appearing targets.
+ *
+ */
+ public AutoTransition() {
+ setOrdering(SEQUENTIALLY);
+ addTransitions(new Fade(Fade.OUT), new Move(), new Fade(Fade.IN));
+ }
+}
diff --git a/core/java/android/view/transition/Crossfade.java b/core/java/android/view/transition/Crossfade.java
new file mode 100644
index 0000000..babf58f
--- /dev/null
+++ b/core/java/android/view/transition/Crossfade.java
@@ -0,0 +1,163 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+
+/**
+ * This transition captures bitmap representations of target views before and
+ * after the scene change and fades between them.
+ *
+ * <p>Note: This transition is not compatible with {@link TextureView}
+ * or {@link SurfaceView}.</p>
+ */
+public class Crossfade extends Transition {
+ // TODO: Add a hook that lets a Transition call user code to query whether it should run on
+ // a given target view. This would save bitmap comparisons in this transition, for example.
+
+ private static final String LOG_TAG = "Crossfade";
+
+ private static final String PROPNAME_BITMAP = "android:crossfade:bitmap";
+ private static final String PROPNAME_DRAWABLE = "android:crossfade:drawable";
+ private static final String PROPNAME_BOUNDS = "android:crossfade:bounds";
+
+ private static RectEvaluator sRectEvaluator = new RectEvaluator();
+
+ @Override
+ protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return false;
+ }
+ final View view = startValues.view;
+ HashMap<String, Object> startVals = startValues.values;
+ HashMap<String, Object> endVals = endValues.values;
+ Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP);
+ Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP);
+ Drawable startDrawable = (Drawable) startVals.get(PROPNAME_DRAWABLE);
+ Drawable endDrawable = (Drawable) endVals.get(PROPNAME_DRAWABLE);
+ if (Transition.DBG) {
+ Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) +
+ " for start, end: " + startBitmap + ", " + endBitmap);
+ }
+ if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) {
+ view.getOverlay().add(endDrawable);
+ view.getOverlay().add(startDrawable);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ HashMap<String, Object> startVals = startValues.values;
+ HashMap<String, Object> endVals = endValues.values;
+
+ final View view = endValues.view;
+ Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS);
+ Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS);
+ final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE);
+ final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE);
+
+ // The transition works by placing the end drawable under the start drawable and
+ // gradually fading out the start drawable. So it's not really a cross-fade, but rather
+ // a reveal of the end scene over time. Also, animate the bounds of both drawables
+ // to mimic the change in the size of the view itself between scenes.
+ ObjectAnimator anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0);
+ anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ // TODO: some way to auto-invalidate views based on drawable changes? callbacks?
+ view.invalidate(startDrawable.getBounds());
+ }
+ });
+ if (Transition.DBG) {
+ Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " +
+ startValues + ", " + endValues);
+ }
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.getOverlay().remove(startDrawable);
+ view.getOverlay().remove(endDrawable);
+ }
+ });
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(anim);
+ if (!startBounds.equals(endBounds)) {
+ if (Transition.DBG) {
+ Log.d(LOG_TAG, "animating from startBounds to endBounds: " +
+ startBounds + ", " + endBounds);
+ }
+ Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds",
+ sRectEvaluator, startBounds, endBounds);
+ Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds",
+ sRectEvaluator, startBounds, endBounds);
+ set.playTogether(anim2);
+ set.playTogether(anim3);
+ }
+ return set;
+ }
+
+ @Override
+ protected void captureValues(TransitionValues values, boolean start) {
+ View view = values.view;
+ values.values.put(PROPNAME_BOUNDS, new Rect(0, 0,
+ view.getWidth(), view.getHeight()));
+
+ if (Transition.DBG) {
+ Log.d(LOG_TAG, "Captured bounds " + values.values.get(PROPNAME_BOUNDS) + ": start = " +
+ start);
+ }
+ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ if (view instanceof TextureView) {
+ bitmap = ((TextureView) view).getBitmap();
+ } else {
+ Canvas c = new Canvas(bitmap);
+ view.draw(c);
+ }
+ values.values.put(PROPNAME_BITMAP, bitmap);
+ // TODO: I don't have resources, can't call the non-deprecated method?
+ BitmapDrawable drawable = new BitmapDrawable(bitmap);
+ // TODO: lrtb will be wrong if the view has transXY set
+ drawable.setBounds(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+ values.values.put(PROPNAME_DRAWABLE, drawable);
+ }
+
+}
diff --git a/core/java/android/view/transition/Fade.java b/core/java/android/view/transition/Fade.java
new file mode 100644
index 0000000..8e4909d
--- /dev/null
+++ b/core/java/android/view/transition/Fade.java
@@ -0,0 +1,209 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and fades views in or out when they become visible
+ * or non-visible. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy.
+ */
+public class Fade extends Visibility {
+
+ private static final String LOG_TAG = "Fade";
+
+ /**
+ * Fading mode used in {@link #Fade(int)} to make the transition
+ * operate on targets that are appearing. Maybe be combined with
+ * {@link #OUT} to fade both in and out.
+ */
+ public static final int IN = 0x1;
+ /**
+ * Fading mode used in {@link #Fade(int)} to make the transition
+ * operate on targets that are disappearing. Maybe be combined with
+ * {@link #IN} to fade both in and out.
+ */
+ public static final int OUT = 0x2;
+
+ private int mFadingMode;
+
+ /**
+ * Constructs a Fade transition that will fade targets in and out.
+ */
+ public Fade() {
+ this(IN | OUT);
+ }
+
+ /**
+ * Constructs a Fade transition that will fade targets in
+ * and/or out, according to the value of fadingMode.
+ *
+ * @param fadingMode The behavior of this transition, a combination of
+ * {@link #IN} and {@link #OUT}.
+ */
+ public Fade(int fadingMode) {
+ mFadingMode = fadingMode;
+ }
+
+ /**
+ * Utility method to handle creating and running the Animator.
+ */
+ private Animator runAnimation(View view, float startAlpha, float endAlpha,
+ Animator.AnimatorListener listener) {
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", startAlpha, endAlpha);
+ if (listener != null) {
+ anim.addListener(listener);
+ }
+ // TODO: Maybe extract a method into Transition to run an animation that handles the
+ // duration/startDelay stuff for all subclasses.
+ return anim;
+ }
+
+ @Override
+ protected boolean preAppear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ if ((mFadingMode & IN) != IN) {
+ return false;
+ }
+ endView.setAlpha(0);
+ return true;
+ }
+
+ @Override
+ protected Animator appear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ if ((mFadingMode & IN) != IN) {
+ return null;
+ }
+ // TODO: hack - retain original value from before preAppear
+ return runAnimation(endView, 0, 1, null);
+ // TODO: end listener to make sure we end at 1 no matter what
+ }
+
+ @Override
+ protected boolean preDisappear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ if ((mFadingMode & OUT) != OUT) {
+ return false;
+ }
+ if (Transition.DBG) {
+ Log.d(LOG_TAG, "Fade.predisappear: startView, startVis, endView, endVis = " +
+ startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
+ }
+ View view;
+ View overlayView = null;
+ View viewToKeep = null;
+ if (endView == null) {
+ // view was removed: add the start view to the Overlay
+ view = startView;
+ overlayView = view;
+ } else {
+ // visibility change
+ if (endVisibility == View.INVISIBLE) {
+ view = endView;
+ viewToKeep = view;
+ } else {
+ // Becoming GONE
+ if (startView == endView) {
+ view = endView;
+ viewToKeep = view;
+ } else {
+ view = startView;
+ overlayView = view;
+ }
+ }
+ }
+ // TODO: add automatic facility to Visibility superclass for keeping views around
+ if (overlayView != null) {
+ // TODO: Need to do this for general case of adding to overlay
+ sceneRoot.getOverlay().add(overlayView);
+ return true;
+ }
+ if (viewToKeep != null) {
+ // TODO: find a different way to do this, like just changing the view to be
+ // VISIBLE for the duration of the transition
+ viewToKeep.setVisibility((View.VISIBLE));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected Animator disappear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ if ((mFadingMode & OUT) != OUT) {
+ return null;
+ }
+ if (Transition.DBG) {
+ Log.d(LOG_TAG, "Fade.disappear: startView, startVis, endView, endVis = " +
+ startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
+ }
+ View view;
+ View overlayView = null;
+ View viewToKeep = null;
+ final int finalVisibility = endVisibility;
+ if (endView == null) {
+ // view was removed: add the start view to the Overlay
+ view = startView;
+ overlayView = view;
+ } else {
+ // visibility change
+ if (endVisibility == View.INVISIBLE) {
+ view = endView;
+ viewToKeep = view;
+ } else {
+ // Becoming GONE
+ if (startView == endView) {
+ view = endView;
+ viewToKeep = view;
+ } else {
+ view = startView;
+ overlayView = view;
+ }
+ }
+ }
+ // TODO: add automatic facility to Visibility superclass for keeping views around
+ final float startAlpha = view.getAlpha();
+ float endAlpha = 0;
+ final View finalView = view;
+ final View finalOverlayView = overlayView;
+ final View finalViewToKeep = viewToKeep;
+ final ViewGroup finalSceneRoot = sceneRoot;
+ final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finalView.setAlpha(startAlpha);
+ // TODO: restore view offset from overlay repositioning
+ if (finalViewToKeep != null) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ if (finalOverlayView != null) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+ }
+ };
+ return runAnimation(view, startAlpha, endAlpha, endListener);
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/view/transition/Move.java b/core/java/android/view/transition/Move.java
new file mode 100644
index 0000000..3bd57bd
--- /dev/null
+++ b/core/java/android/view/transition/Move.java
@@ -0,0 +1,299 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.RectEvaluator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+
+/**
+ * This transition captures the layout bounds of target views before and after
+ * the scene change and animates those changes during the transition.
+ */
+public class Move extends Transition {
+
+ private static final String PROPNAME_BOUNDS = "android:move:bounds";
+ private static final String PROPNAME_PARENT = "android:move:parent";
+ private static final String PROPNAME_WINDOW_X = "android:move:windowX";
+ private static final String PROPNAME_WINDOW_Y = "android:move:windowY";
+ int[] tempLocation = new int[2];
+ boolean mResizeClip = false;
+ boolean mReparent = false;
+
+ private static RectEvaluator sRectEvaluator = new RectEvaluator();
+
+ public void setResizeClip(boolean resizeClip) {
+ mResizeClip = resizeClip;
+ }
+
+ /**
+ * Setting this flag tells Move to track the before/after parent
+ * of every view using this transition. The flag is not enabled by
+ * default because it requires the parent instances to be the same
+ * in the two scenes or else all parents must use ids to allow
+ * the transition to determine which parents are the same.
+ *
+ * @param reparent true if the transition should track the parent
+ * container of target views and animate parent changes.
+ */
+ public void setReparent(boolean reparent) {
+ mReparent = reparent;
+ }
+
+ @Override
+ protected void captureValues(TransitionValues values, boolean start) {
+ View view = values.view;
+ values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
+ view.getRight(), view.getBottom()));
+ values.values.put(PROPNAME_PARENT, values.view.getParent());
+ values.view.getLocationInWindow(tempLocation);
+ values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
+ values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
+ }
+
+ @Override
+ protected Animator play(final ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ final View view = endValues.view;
+ if (view.getParent() == null) {
+ // TODO: Might want to make it possible to Move an disappearing view.
+ // This workaround is here because if a parallel Fade is not running on the view
+ // Then it won't get added to the hierarchy and the animator below will not fire,
+ // causing the transition to not end
+ return null;
+ }
+ // TODO: need to handle non-VG case?
+ ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
+ ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
+ if (startParent == null || endParent == null) {
+ return null;
+ }
+ boolean parentsEqual = (startParent == endParent) ||
+ (startParent.getId() == endParent.getId());
+ if (!mReparent || parentsEqual) {
+ // Common case - view belongs to the same layout before/after. Just animate its bounds
+ Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+ Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+ int startLeft = startBounds.left;
+ int endLeft = endBounds.left;
+ int startTop = startBounds.top;
+ int endTop = endBounds.top;
+ int startRight = startBounds.right;
+ int endRight = endBounds.right;
+ int startBottom = startBounds.bottom;
+ int endBottom = endBounds.bottom;
+ int startWidth = startRight - startLeft;
+ int startHeight = startBottom - startTop;
+ int endWidth = endRight - endLeft;
+ int endHeight = endBottom - endTop;
+ int numChanges = 0;
+ if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) {
+ if (startLeft != endLeft) ++numChanges;
+ if (startTop != endTop) ++numChanges;
+ if (startRight != endRight) ++numChanges;
+ if (startBottom != endBottom) ++numChanges;
+ }
+ if (numChanges > 0) {
+ if (!mResizeClip) {
+ PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
+ int pvhIndex = 0;
+ if (startLeft != endLeft) {
+ pvh[pvhIndex++] = PropertyValuesHolder.ofInt("left", startLeft, endLeft);
+ }
+ if (startTop != endTop) {
+ pvh[pvhIndex++] = PropertyValuesHolder.ofInt("top", startTop, endTop);
+ }
+ if (startRight != endRight) {
+ pvh[pvhIndex++] = PropertyValuesHolder.ofInt("right",
+ startRight, endRight);
+ }
+ if (startBottom != endBottom) {
+ pvh[pvhIndex++] = PropertyValuesHolder.ofInt("bottom",
+ startBottom, endBottom);
+ }
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
+ if (view.getParent() instanceof ViewGroup) {
+ final ViewGroup parent = (ViewGroup) view.getParent();
+ parent.suppressLayout(true);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ parent.suppressLayout(false);
+ }
+ });
+ }
+ return anim;
+ } else {
+ // Animate location with translationX/Y and size with clip bounds
+ float transXDelta = endLeft - startLeft;
+ float transYDelta = endTop - startTop;
+ int widthDelta = endWidth - startWidth;
+ int heightDelta = endHeight - startHeight;
+ numChanges = 0;
+ if (transXDelta != 0) numChanges++;
+ if (transYDelta != 0) numChanges++;
+ if (widthDelta != 0 || heightDelta != 0) numChanges++;
+ PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
+ int pvhIndex = 0;
+ if (transXDelta != 0) {
+ pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationX",
+ view.getTranslationX(), 0);
+ }
+ if (transYDelta != 0) {
+ pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationY",
+ view.getTranslationY(), 0);
+ }
+ if (widthDelta != 0 || heightDelta != 0) {
+ Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
+ Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
+ pvh[pvhIndex++] = PropertyValuesHolder.ofObject("clipBounds",
+ sRectEvaluator, tempStartBounds, tempEndBounds);
+ }
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
+ if (view.getParent() instanceof ViewGroup) {
+ final ViewGroup parent = (ViewGroup) view.getParent();
+ parent.suppressLayout(true);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ parent.suppressLayout(false);
+ }
+ });
+ }
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setClipBounds(null);
+ }
+ });
+ return anim;
+ }
+ }
+ } else {
+ return (ObjectAnimator) endValues.values.get("drawableAnim");
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean prePlay(final ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return false;
+ }
+ HashMap<String, Object> startParentVals = startValues.values;
+ HashMap<String, Object> endParentVals = endValues.values;
+ ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
+ ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
+ if (startParent == null || endParent == null) {
+ return false;
+ }
+ final View view = endValues.view;
+ boolean parentsEqual = (startParent == endParent) ||
+ (startParent.getId() == endParent.getId());
+ // TODO: Might want reparenting to be separate/subclass transition, or at least
+ // triggered by a property on Move. Otherwise, we're forcing the requirement that
+ // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect
+ // of reparenting the views.
+ if (!mReparent || parentsEqual) {
+ Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+ Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+ int startLeft = startBounds.left;
+ int endLeft = endBounds.left;
+ int startTop = startBounds.top;
+ int endTop = endBounds.top;
+ int startRight = startBounds.right;
+ int endRight = endBounds.right;
+ int startBottom = startBounds.bottom;
+ int endBottom = endBounds.bottom;
+ int startWidth = startRight - startLeft;
+ int startHeight = startBottom - startTop;
+ int endWidth = endRight - endLeft;
+ int endHeight = endBottom - endTop;
+ int numChanges = 0;
+ if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) {
+ if (startLeft != endLeft) ++numChanges;
+ if (startTop != endTop) ++numChanges;
+ if (startRight != endRight) ++numChanges;
+ if (startBottom != endBottom) ++numChanges;
+ }
+ if (numChanges > 0) {
+ if (!mResizeClip) {
+ if (startLeft != endLeft) view.setLeft(startLeft);
+ if (startTop != endTop) view.setTop(startTop);
+ if (startRight != endRight) view.setRight(startRight);
+ if (startBottom != endBottom) view.setBottom(startBottom);
+ } else {
+ if (startWidth != endWidth) view.setRight(endLeft +
+ Math.max(startWidth, endWidth));
+ if (startHeight != endHeight) view.setBottom(endTop +
+ Math.max(startHeight, endHeight));
+ // TODO: don't clobber TX/TY
+ if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft);
+ if (startTop != endTop) view.setTranslationY(startTop - endTop);
+ }
+ return true;
+ }
+ } else {
+ int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
+ int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
+ int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
+ int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
+ // TODO: also handle size changes: check bounds and animate size changes
+ if (startX != endX || startY != endY) {
+ sceneRoot.getLocationInWindow(tempLocation);
+ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ final BitmapDrawable drawable = new BitmapDrawable(bitmap);
+ view.setVisibility(View.INVISIBLE);
+ sceneRoot.getOverlay().add(drawable);
+ Rect startBounds = new Rect(startX - tempLocation[0], startY - tempLocation[1],
+ startX - tempLocation[0] + view.getWidth(),
+ startY - tempLocation[1] + view.getHeight());
+ Rect endBounds = new Rect(endX - tempLocation[0], endY - tempLocation[1],
+ endX - tempLocation[0] + view.getWidth(),
+ endY - tempLocation[1] + view.getHeight());
+ ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds",
+ sRectEvaluator, startBounds, endBounds);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ sceneRoot.getOverlay().remove(drawable);
+ view.setVisibility(View.VISIBLE);
+ }
+ });
+ endParentVals.put("drawableAnim", anim);
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/core/java/android/view/transition/Recolor.java b/core/java/android/view/transition/Recolor.java
new file mode 100644
index 0000000..7048be9
--- /dev/null
+++ b/core/java/android/view/transition/Recolor.java
@@ -0,0 +1,118 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+/**
+ * This transition tracks changes during scene changes to the
+ * {@link View#setBackground(android.graphics.drawable.Drawable) background}
+ * property of its target views (when the background is a
+ * {@link ColorDrawable}, as well as the
+ * {@link TextView#setTextColor(android.content.res.ColorStateList)
+ * color} of the text for target TextViews. If the color changes between
+ * scenes, the color change is animated.
+ */
+public class Recolor extends Transition {
+
+ private static final String PROPNAME_BACKGROUND = "android:recolor:background";
+ private static final String PROPNAME_TEXT_COLOR = "android:recolor:textColor";
+
+ @Override
+ protected void captureValues(TransitionValues values, boolean start) {
+ values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
+ if (values.view instanceof TextView) {
+ values.values.put(PROPNAME_TEXT_COLOR, ((TextView)values.view).getCurrentTextColor());
+ }
+ }
+
+ @Override
+ protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return false;
+ }
+ final View view = endValues.view;
+ Drawable startBackground = (Drawable) startValues.values.get(PROPNAME_BACKGROUND);
+ Drawable endBackground = (Drawable) endValues.values.get(PROPNAME_BACKGROUND);
+ boolean changed = false;
+ if (startBackground instanceof ColorDrawable && endBackground instanceof ColorDrawable) {
+ ColorDrawable startColor = (ColorDrawable) startBackground;
+ ColorDrawable endColor = (ColorDrawable) endBackground;
+ if (startColor.getColor() != endColor.getColor()) {
+ endColor.setColor(startColor.getColor());
+ changed = true;
+ }
+ }
+ if (view instanceof TextView) {
+ TextView textView = (TextView) view;
+ int start = (Integer) startValues.values.get(PROPNAME_TEXT_COLOR);
+ int end = (Integer) endValues.values.get(PROPNAME_TEXT_COLOR);
+ if (start != end) {
+ textView.setTextColor(end);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ ObjectAnimator anim = null;
+ final View view = endValues.view;
+ HashMap<String, Object> startVals = startValues.values;
+ HashMap<String, Object> endVals = endValues.values;
+ Drawable startBackground = (Drawable) startVals.get(PROPNAME_BACKGROUND);
+ Drawable endBackground = (Drawable) endVals.get(PROPNAME_BACKGROUND);
+ if (startBackground instanceof ColorDrawable && endBackground instanceof ColorDrawable) {
+ ColorDrawable startColor = (ColorDrawable) startBackground;
+ ColorDrawable endColor = (ColorDrawable) endBackground;
+ if (startColor.getColor() != endColor.getColor()) {
+ anim = ObjectAnimator.ofObject(endBackground, "color",
+ new ArgbEvaluator(), startColor.getColor(), endColor.getColor());
+ if (getStartDelay() > 0) {
+ endColor.setColor(startColor.getColor());
+ }
+ }
+ }
+ if (view instanceof TextView) {
+ TextView textView = (TextView) view;
+ int start = (Integer) startValues.values.get(PROPNAME_TEXT_COLOR);
+ int end = (Integer) endValues.values.get(PROPNAME_TEXT_COLOR);
+ if (start != end) {
+ anim = ObjectAnimator.ofObject(textView, "textColor",
+ new ArgbEvaluator(), start, end);
+ if (getStartDelay() > 0) {
+ textView.setTextColor(end);
+ }
+ }
+ }
+ return anim;
+ }
+}
diff --git a/core/java/android/view/transition/Rotate.java b/core/java/android/view/transition/Rotate.java
new file mode 100644
index 0000000..b42fbe5
--- /dev/null
+++ b/core/java/android/view/transition/Rotate.java
@@ -0,0 +1,67 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * This transition captures the rotation property of targets before and after
+ * the scene change and animates any changes.
+ */
+public class Rotate extends Transition {
+
+ private static final String PROPNAME_ROTATION = "android:rotate:rotation";
+
+ @Override
+ protected void captureValues(TransitionValues values, boolean start) {
+ values.values.put(PROPNAME_ROTATION, values.view.getRotation());
+ }
+
+ @Override
+ protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return false;
+ }
+ final View view = endValues.view;
+ float startRotation = (Float) startValues.values.get(PROPNAME_ROTATION);
+ float endRotation = (Float) endValues.values.get(PROPNAME_ROTATION);
+ if (startRotation != endRotation) {
+ view.setRotation(startRotation);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ final View view = endValues.view;
+ float startRotation = (Float) startValues.values.get(PROPNAME_ROTATION);
+ float endRotation = (Float) endValues.values.get(PROPNAME_ROTATION);
+ if (startRotation != endRotation) {
+ return ObjectAnimator.ofFloat(view, View.ROTATION,
+ startRotation, endRotation);
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/view/transition/Scene.java b/core/java/android/view/transition/Scene.java
new file mode 100644
index 0000000..62cb9d3
--- /dev/null
+++ b/core/java/android/view/transition/Scene.java
@@ -0,0 +1,191 @@
+/*
+ * 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.transition;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+/**
+ * A scene represents the collection of values that various properties in the
+ * View hierarchy will have when the scene is applied. A Scene can be
+ * configured to automatically run a Transition when it is applied, which will
+ * animate the various property changes that take place during the
+ * scene change.
+ */
+public final class Scene {
+
+ private Context mContext;
+ private int mLayoutId = -1;
+ private ViewGroup mSceneRoot;
+ private ViewGroup mLayout; // alternative to layoutId
+ Runnable mEnterAction, mExitAction;
+
+ /**
+ * Constructs a Scene with no information about how values will change
+ * when this scene is applied. This constructor might be used when
+ * a Scene is created with the intention of being dynamically configured,
+ * through setting {@link #setEnterAction(Runnable)} and possibly
+ * {@link #setExitAction(Runnable)}.
+ *
+ * @param sceneRoot The root of the hierarchy in which scene changes
+ * and transitions will take place.
+ */
+ public Scene(ViewGroup sceneRoot) {
+ mSceneRoot = sceneRoot;
+ }
+
+ /**
+ * Constructs a Scene which, when entered, will remove any
+ * children from the sceneRoot container and will inflate and add
+ * the hierarchy specified by the layoutId resource file.
+ *
+ * @param sceneRoot The root of the hierarchy in which scene changes
+ * and transitions will take place.
+ * @param layoutId The id of a resource file that defines the view
+ * hierarchy of this scene.
+ * @param context The context used in the process of inflating
+ * the layout resource.
+ */
+ public Scene(ViewGroup sceneRoot, int layoutId, Context context) {
+ mContext = context;
+ mSceneRoot = sceneRoot;
+ mLayoutId = layoutId;
+ }
+
+ /**
+ * Constructs a Scene which, when entered, will remove any
+ * children from the sceneRoot container and add the layout
+ * object as a new child of that container.
+ *
+ * @param sceneRoot The root of the hierarchy in which scene changes
+ * and transitions will take place.
+ * @param layout The view hiearrchy of this scene, added as a child
+ * of sceneRoot when this scene is entered.
+ */
+ public Scene(ViewGroup sceneRoot, ViewGroup layout) {
+ mSceneRoot = sceneRoot;
+ mLayout = layout;
+ }
+
+ /**
+ * Gets the root of the scene, which is the root of the view hierarchy
+ * affected by changes due to this scene, and which will be animated
+ * when this scene is entered.
+ *
+ * @return The root of the view hierarchy affected by this scene.
+ */
+ public ViewGroup getSceneRoot() {
+ return mSceneRoot;
+ }
+
+ /**
+ * Exits this scene, if it is the {@link ViewGroup#getCurrentScene()
+ * currentScene} on the scene's {@link #getSceneRoot() scene root}.
+ * Exiting a scene involves removing the layout added if the scene
+ * has either a layoutId or layout view group (set at construction
+ * time) and running the {@link #setExitAction(Runnable) exit action}
+ * if there is one.
+ */
+ public void exit() {
+ if (mSceneRoot.getCurrentScene() == this) {
+ if (mLayoutId >= 0 || mLayout != null) {
+ // Undo layout change caused by entering this scene
+ getSceneRoot().removeAllViews();
+ }
+ if (mExitAction != null) {
+ mExitAction.run();
+ }
+ }
+ }
+
+ /**
+ * Enters this scene, which entails changing all values that
+ * are specified by this scene. These may be values associated
+ * with a layout view group or layout resource file which will
+ * now be added to the scene root, or it may be values changed by
+ * an {@link #setEnterAction(Runnable)} enter action}, or a
+ * combination of the these. No transition will be run when the
+ * scene is entered. To get transition behavior in scene changes,
+ * use one of the methods in {@link TransitionManager} instead.
+ */
+ public void enter() {
+
+ // Apply layout change, if any
+ if (mLayoutId >= 0 || mLayout != null) {
+ // redundant with exit() action of previous scene, but must
+ // empty out that parent container before adding to it
+ getSceneRoot().removeAllViews();
+
+ if (mLayoutId >= 0) {
+ LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
+ } else {
+ mSceneRoot.addView(mLayout);
+ }
+ }
+
+ // Notify next scene that it is entering. Subclasses may override to configure scene.
+ if (mEnterAction != null) {
+ mEnterAction.run();
+ }
+
+ mSceneRoot.setCurrentScene(this );
+ }
+
+ /**
+ * Scenes that are not defined with layout resources or
+ * hierarchies, or which need to perform additional steps
+ * after those hierarchies are changed to, should set an enter
+ * action, and possibly an exit action as well. An enter action
+ * will cause Scene to call back into application code to do
+ * anything else the application needs after transitions have
+ * captured pre-change values and after any other scene changes
+ * have been applied, such as the layout (if any) being added to
+ * the view hierarchy. After this method is called, Transitions will
+ * be played.
+ *
+ * @param action The runnable whose {@link Runnable#run() run()} method will
+ * be called when this scene is entered
+ * @see #setExitAction(Runnable)
+ * @see Scene#Scene(ViewGroup, int, Context)
+ * @see Scene#Scene(ViewGroup, ViewGroup)
+ */
+ public void setEnterAction(Runnable action) {
+ mEnterAction = action;
+ }
+
+ /**
+ * Scenes that are not defined with layout resources or
+ * hierarchies, or which need to perform additional steps
+ * after those hierarchies are changed to, should set an enter
+ * action, and possibly an exit action as well. An exit action
+ * will cause Scene to call back into application code to do
+ * anything the application needs to do after applicable transitions have
+ * captured pre-change values, but before any other scene changes
+ * have been applied, such as the new layout (if any) being added to
+ * the view hierarchy. After this method is called, the next scene
+ * will be entered, including a call to {@link #setEnterAction(Runnable)}
+ * if an enter action is set.
+ *
+ * @see #setEnterAction(Runnable)
+ * @see Scene#Scene(ViewGroup, int, Context)
+ * @see Scene#Scene(ViewGroup, ViewGroup)
+ */
+ public void setExitAction(Runnable action) {
+ mExitAction = action;
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/view/transition/Slide.java b/core/java/android/view/transition/Slide.java
new file mode 100644
index 0000000..8630ee2
--- /dev/null
+++ b/core/java/android/view/transition/Slide.java
@@ -0,0 +1,70 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This transition captures the visibility of target objects before and
+ * after a scene change and animates any changes by sliding the target
+ * objects into or out of place.
+ */
+public class Slide extends Visibility {
+
+ // TODO: Add parameter for sliding factor - it's hard-coded below
+
+ private static final TimeInterpolator sAccelerator = new AccelerateInterpolator();
+ private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
+
+ @Override
+ protected Animator appear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(endView, View.TRANSLATION_Y,
+ -2 * endView.getHeight(), 0);
+ anim.setInterpolator(sDecelerator);
+ return anim;
+ }
+
+ @Override
+ protected boolean preAppear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ endView.setTranslationY(-2 * endView.getHeight());
+ return true;
+ }
+
+ @Override
+ protected boolean preDisappear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ startView.setTranslationY(0);
+ return true;
+ }
+
+ @Override
+ protected Animator disappear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(startView, View.TRANSLATION_Y, 0,
+ -2 * startView.getHeight());
+ anim.setInterpolator(sAccelerator);
+ return anim;
+ }
+
+}
diff --git a/core/java/android/view/transition/TextChange.java b/core/java/android/view/transition/TextChange.java
new file mode 100644
index 0000000..0ba2412
--- /dev/null
+++ b/core/java/android/view/transition/TextChange.java
@@ -0,0 +1,90 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+/**
+ * This transition tracks changes to the text in TextView targets. If the text
+ * changes between the start and end scenes, the transition ensures that the
+ * starting text stays until the transition ends, at which point it changes
+ * to the end text. This is useful in situations where you want to resize a
+ * text view to its new size before displaying the text that goes there.
+ */
+public class TextChange extends Transition {
+ private static final String PROPNAME_TEXT = "android:textchange:text";
+
+ // TODO: think about other options we could have here, like cross-fading the text, or fading
+ // it out/in. These could be parameters to supply to the constructors (and xml attributes).
+
+ @Override
+ protected void captureValues(TransitionValues values, boolean start) {
+ if (values.view instanceof TextView) {
+ TextView textview = (TextView) values.view;
+ values.values.put(PROPNAME_TEXT, textview.getText());
+ }
+ }
+
+ @Override
+ protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null || !(endValues.view instanceof TextView)) {
+ return false;
+ }
+ final TextView view = (TextView) endValues.view;
+ HashMap<String, Object> startVals = startValues.values;
+ HashMap<String, Object> endVals = endValues.values;
+ String startText = (String) startVals.get(PROPNAME_TEXT);
+ String endText = (String) endVals.get(PROPNAME_TEXT);
+ if (!startText.equals(endText)) {
+ view.setText(startText);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null || !(endValues.view instanceof TextView)) {
+ return null;
+ }
+ final TextView view = (TextView) endValues.view;
+ HashMap<String, Object> startVals = startValues.values;
+ HashMap<String, Object> endVals = endValues.values;
+ final String startText = (String) startVals.get(PROPNAME_TEXT);
+ final String endText = (String) endVals.get(PROPNAME_TEXT);
+ if (!startText.equals(endText)) {
+ // This noop animation is just used to keep the text in its start state
+ // until the transition ends
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setText(endText);
+ }
+ });
+ return anim;
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/view/transition/Transition.java b/core/java/android/view/transition/Transition.java
new file mode 100644
index 0000000..150c218
--- /dev/null
+++ b/core/java/android/view/transition/Transition.java
@@ -0,0 +1,911 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOverlay;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A Transition holds information about animations that will be run on its
+ * targets during a scene change. Subclasses of this abstract class may
+ * choreograph several child transitions ({@link TransitionGroup} or they may
+ * perform custom animations themselves. Any Transition has two main jobs:
+ * (1) capture property values, and (2) play animations based on changes to
+ * captured property values. A custom transition knows what property values
+ * on View objects are of interest to it, and also knows how to animate
+ * changes to those values. For example, the {@link Fade} transition tracks
+ * changes to visibility-related properties and is able to construct and run
+ * animations that fade items in or out based on changes to those properties.
+ *
+ * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
+ * or {@link TextureView}, due to the way that these views are displayed
+ * on the screen. For SurfaceView, the problem is that the view is updated from
+ * a non-UI thread, so changes to the view due to transitions (such as moving
+ * and resizing the view) may be out of sync with the display inside those bounds.
+ * TextureView is more compatible with transitions in general, but some
+ * specific transitions (such as {@link Crossfade}) may not be compatible
+ * with TextureView because they rely on {@link ViewOverlay} functionality,
+ * which does not currently work with TextureView.</p>
+ */
+public abstract class Transition {
+
+ private static final String LOG_TAG = "Transition";
+ static final boolean DBG = false;
+
+ long mStartDelay = -1;
+ long mDuration = -1;
+ TimeInterpolator mInterpolator = null;
+ int[] mTargetIds;
+ View[] mTargets;
+ // TODO: sparse arrays instead of hashmaps?
+ private HashMap<View, TransitionValues> mStartValues =
+ new HashMap<View, TransitionValues>();
+ private SparseArray<TransitionValues> mStartIdValues = new SparseArray<TransitionValues>();
+ private LongSparseArray<TransitionValues> mStartItemIdValues =
+ new LongSparseArray<TransitionValues>();
+ private HashMap<View, TransitionValues> mEndValues =
+ new HashMap<View, TransitionValues>();
+ private SparseArray<TransitionValues> mEndIdValues = new SparseArray<TransitionValues>();
+ private LongSparseArray<TransitionValues> mEndItemIdValues =
+ new LongSparseArray<TransitionValues>();
+
+ // Used to carry data between preplay() and play(), cleared before every scene transition
+ private ArrayList<TransitionValues> mPlayStartValuesList = new ArrayList<TransitionValues>();
+ private ArrayList<TransitionValues> mPlayEndValuesList = new ArrayList<TransitionValues>();
+
+ // Number of per-target instances of this Transition currently running. This count is
+ // determined by calls to startTransition() and endTransition()
+ int mNumInstances = 0;
+
+
+ /**
+ * The set of listeners to be sent transition lifecycle events.
+ */
+ ArrayList<TransitionListener> mListeners = null;
+
+ /**
+ * Constructs a Transition object with no target objects. A transition with
+ * no targets defaults to running on all target objects in the scene hierarchy
+ * (if the transition is not contained in a TransitionGroup), or all target
+ * objects passed down from its parent (if it is in a TransitionGroup).
+ */
+ public Transition() {}
+
+ /**
+ * Sets the duration of this transition. By default, there is no duration
+ * (indicated by a negative number), which means that the Animator created by
+ * the transition will have its own specified duration. If the duration of a
+ * Transition is set, that duration will override the Animator duration.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @return This transition object.
+ */
+ public Transition setDuration(long duration) {
+ mDuration = duration;
+ return this;
+ }
+
+ public long getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Sets the startDelay of this transition. By default, there is no delay
+ * (indicated by a negative number), which means that the Animator created by
+ * the transition will have its own specified startDelay. If the delay of a
+ * Transition is set, that delay will override the Animator delay.
+ *
+ * @param startDelay The length of the delay, in milliseconds.
+ */
+ public void setStartDelay(long startDelay) {
+ mStartDelay = startDelay;
+ }
+
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ /**
+ * Sets the interpolator of this transition. By default, the interpolator
+ * is null, which means that the Animator created by the transition
+ * will have its own specified interpolator. If the interpolator of a
+ * Transition is set, that interpolator will override the Animator interpolator.
+ *
+ * @param interpolator The time interpolator used by the transition
+ */
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * This method is called by the transition's parent (all the way up to the
+ * topmost Transition in the hierarchy) with the sceneRoot and start/end
+ * values that the transition may need to run animations on its target
+ * views. The method is called for every applicable target object, which
+ * is stored in the {@link TransitionValues#view} field. When the method
+ * results in an animation needing to be run, the transition will construct
+ * the appropriate {@link Animator} object and return it. The transition
+ * mechanism will apply any applicable duration, startDelay, and interpolator
+ * to that animation and start it. Returning null from the method tells the
+ * transition engine that there is no animation to be played (TransitionGroup
+ * will return null because any applicable animations were started on its child
+ * transitions already and there is no animation to be run on the group itself).
+ *
+ * @param sceneRoot
+ * @param startValues
+ * @param endValues
+ * @return Animator The animation to run.
+ */
+ protected abstract Animator play(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues);
+
+ /**
+ * This method is called by the transition's parent (all the way up to the
+ * topmost Transition in the hierarchy) with the sceneRoot and start/end
+ * values that the transition may need to set things up at the start of a
+ * Transition. For example, if an overall Transition consists of several
+ * child transitions in sequence, then some of the child transitions may
+ * want to set initial values on target views prior to the overall
+ * Transition commencing, to put them in an appropriate scene for the
+ * delay between that start and the child Transition start time. For
+ * example, a transition that fades an item in may wish to set the starting
+ * alpha value to 0, to avoid it blinking in prior to the transition
+ * actually starting the animation. This is necessary because the scene
+ * change that triggers the Transition will automatically set the end-scene
+ * on all target views, so a Transition that wants to animate from a
+ * different value should set that value in the preplay() method.
+ *
+ * <p>Additionally, a Transition can perform logic to determine whether
+ * the transition needs to run on the given target and start/end values.
+ * For example, a transition that resizes objects on the screen may wish
+ * to avoid running for views which are not present in either the start
+ * or end scenes. A return value of <code>false</code> indicates that
+ * the transition should not run, and there will be no ensuing call to the
+ * {@link #play(ViewGroup, TransitionValues, TransitionValues)} method during
+ * this scene change. The default implementation returns true.</p>
+ *
+ * <p>The method is called for every applicable target object, which is
+ * stored in the {@link TransitionValues#view} field.</p>
+ *
+ * @param sceneRoot
+ * @param startValues
+ * @param endValues
+ * @return True if the Transition's {@link #play(ViewGroup,
+ * TransitionValues, TransitionValues) play()} method should be called
+ * during this scene change, false otherwise.
+ */
+ protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ return true;
+ }
+
+ /**
+ * This version of prePlay() is called with the entire set of start/end
+ * values. The implementation in Transition iterates through these lists
+ * and calls {@link #prePlay(ViewGroup, TransitionValues, TransitionValues)}
+ * with each set of start/end values on this transition. The
+ * TransitionGroup subclass overrides this method and delegates it to
+ * each of its children in succession. The intention in splitting
+ * preplay() out from play() is to allow all Transitions in the tree to
+ * set up the appropriate start scene for their target objects prior to
+ * any calls to play(), which is necessary when there is a sequential
+ * Transition, where a child transition which is not the first may want to
+ * set up a target's scene prior to the overall Transition start.
+ *
+ * @hide
+ */
+ protected void prePlay(ViewGroup sceneRoot, HashMap<View, TransitionValues> startValues,
+ SparseArray<TransitionValues> startIdValues,
+ LongSparseArray<TransitionValues> startItemIdValues,
+ HashMap<View, TransitionValues> endValues,
+ SparseArray<TransitionValues> endIdValues,
+ LongSparseArray<TransitionValues> endItemIdValues) {
+ mPlayStartValuesList.clear();
+ mPlayEndValuesList.clear();
+ HashMap<View, TransitionValues> endCopy = new HashMap<View, TransitionValues>(endValues);
+ SparseArray<TransitionValues> endIdCopy =
+ new SparseArray<TransitionValues>(endIdValues.size());
+ for (int i = 0; i < endIdValues.size(); ++i) {
+ int id = endIdValues.keyAt(i);
+ endIdCopy.put(id, endIdValues.valueAt(i));
+ }
+ LongSparseArray<TransitionValues> endItemIdCopy =
+ new LongSparseArray<TransitionValues>(endItemIdValues.size());
+ for (int i = 0; i < endItemIdValues.size(); ++i) {
+ long id = endItemIdValues.keyAt(i);
+ endItemIdCopy.put(id, endItemIdValues.valueAt(i));
+ }
+ // Walk through the start values, playing everything we find
+ // Remove from the end set as we go
+ ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
+ ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
+ for (View view : startValues.keySet()) {
+ TransitionValues start = null;
+ TransitionValues end = null;
+ boolean isInListView = false;
+ if (view.getParent() instanceof ListView) {
+ isInListView = true;
+ }
+ if (!isInListView) {
+ int id = view.getId();
+ start = startValues.get(view) != null ?
+ startValues.get(view) : startIdValues.get(id);
+ if (endValues.get(view) != null) {
+ end = endValues.get(view);
+ endCopy.remove(view);
+ } else {
+ end = endIdValues.get(id);
+ View removeView = null;
+ for (View viewToRemove : endCopy.keySet()) {
+ if (viewToRemove.getId() == id) {
+ removeView = viewToRemove;
+ }
+ }
+ if (removeView != null) {
+ endCopy.remove(removeView);
+ }
+ }
+ endIdCopy.remove(id);
+ if (isValidTarget(view, id)) {
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ } else {
+ ListView parent = (ListView) view.getParent();
+ if (parent.getAdapter().hasStableIds()) {
+ int position = parent.getPositionForView(view);
+ long itemId = parent.getItemIdAtPosition(position);
+ start = startItemIdValues.get(itemId);
+ endItemIdCopy.remove(itemId);
+ // TODO: deal with targetIDs for itemIDs for ListView items
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ }
+ }
+ int startItemIdCopySize = startItemIdValues.size();
+ for (int i = 0; i < startItemIdCopySize; ++i) {
+ long id = startItemIdValues.keyAt(i);
+ if (isValidTarget(null, id)) {
+ TransitionValues start = startItemIdValues.get(id);
+ TransitionValues end = endItemIdValues.get(id);
+ endItemIdCopy.remove(id);
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ }
+ // Now walk through the remains of the end set
+ for (View view : endCopy.keySet()) {
+ int id = view.getId();
+ if (isValidTarget(view, id)) {
+ TransitionValues start = startValues.get(view) != null ?
+ startValues.get(view) : startIdValues.get(id);
+ TransitionValues end = endCopy.get(view);
+ endIdCopy.remove(id);
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ }
+ int endIdCopySize = endIdCopy.size();
+ for (int i = 0; i < endIdCopySize; ++i) {
+ int id = endIdCopy.keyAt(i);
+ if (isValidTarget(null, id)) {
+ TransitionValues start = startIdValues.get(id);
+ TransitionValues end = endIdCopy.get(id);
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ }
+ int endItemIdCopySize = endItemIdCopy.size();
+ for (int i = 0; i < endItemIdCopySize; ++i) {
+ long id = endItemIdCopy.keyAt(i);
+ // TODO: Deal with targetIDs and itemIDs
+ TransitionValues start = startItemIdValues.get(id);
+ TransitionValues end = endItemIdCopy.get(id);
+ startValuesList.add(start);
+ endValuesList.add(end);
+ }
+ for (int i = 0; i < startValuesList.size(); ++i) {
+ TransitionValues start = startValuesList.get(i);
+ TransitionValues end = endValuesList.get(i);
+ // TODO: what to do about targetIds and itemIds?
+ if (prePlay(sceneRoot, start, end)) {
+ // Note: we've already done the check against targetIDs in these lists
+ mPlayStartValuesList.add(start);
+ mPlayEndValuesList.add(end);
+ }
+ }
+ }
+
+ /**
+ * Internal utility method for checking whether a given view/id
+ * is valid for this transition, where "valid" means that either
+ * the Transition has no target/targetId list (the default, in which
+ * cause the transition should act on all views in the hiearchy), or
+ * the given view is in the target list or the view id is in the
+ * targetId list. If the target parameter is null, then the target list
+ * is not checked (this is in the case of ListView items, where the
+ * views are ignored and only the ids are used).
+ */
+ boolean isValidTarget(View target, long targetId) {
+ if (mTargetIds == null && mTargets == null) {
+ return true;
+ }
+ if (mTargetIds != null) {
+ for (int i = 0; i < mTargetIds.length; ++i) {
+ if (mTargetIds[i] == targetId) {
+ return true;
+ }
+ }
+ }
+ if (target != null && mTargets != null) {
+ for (int i = 0; i < mTargets.length; ++i) {
+ if (mTargets[i] == target) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This version of play() is called with the entire set of start/end
+ * values. The implementation in Transition iterates through these lists
+ * and calls {@link #play(ViewGroup, TransitionValues, TransitionValues)}
+ * with each set of start/end values on this transition. The
+ * TransitionGroup subclass overrides this method and delegates it to
+ * each of its children in succession.
+ *
+ * @hide
+ */
+ protected void play(ViewGroup sceneRoot,
+ final HashMap<View, TransitionValues> startValues,
+ final SparseArray<TransitionValues> startIdValues,
+ final LongSparseArray<TransitionValues> startItemIdValues,
+ final HashMap<View, TransitionValues> endValues,
+ final SparseArray<TransitionValues> endIdValues,
+ final LongSparseArray<TransitionValues> endItemIdValues) {
+
+ startTransition();
+ // Now walk the list of TransitionValues, calling play for each pair
+ for (int i = 0; i < mPlayStartValuesList.size(); ++i) {
+ TransitionValues start = mPlayStartValuesList.get(i);
+ TransitionValues end = mPlayEndValuesList.get(i);
+ startTransition();
+ animate(play(sceneRoot, start, end));
+ }
+ mPlayStartValuesList.clear();
+ mPlayEndValuesList.clear();
+ endTransition();
+ }
+
+ /**
+ * Captures the current scene of values for the properties that this
+ * transition monitors. These values can be either the start or end
+ * values used in a subsequent call to
+ * {@link #play(ViewGroup, TransitionValues, TransitionValues)}, as indicated by
+ * <code>start</code>. The main concern for an implementation is what the
+ * properties are that the transition cares about and what the values are
+ * for all of those properties. The start and end values will be compared
+ * later during the preplay and play() methods to determine what, if any,
+ * animations, should be run.
+ *
+ * @param transitionValues The holder any values that the Transition
+ * wishes to store. Values are stored in the fields of this
+ * TransitionValues object, according to their type, and are keyed from
+ * a String value. For example, to start a view's rotation value,
+ * a Transition might call
+ * <code>transitionValues.floatValues.put("rotation", view.getRotation())
+ * </code>. The target <code>View</code> will already be stored in
+ * the transitionValues structure when this method is called. The other
+ * fields in TransitionValues, e.g. <code>floatValues</code>,
+ * may need to be instantiated if they have not yet been created.
+ */
+ protected abstract void captureValues(TransitionValues transitionValues, boolean start);
+
+ /**
+ * Sets the ids of target views that this Transition is interested in
+ * animating. By default, there are no targetIds, and a Transition will
+ * listen for changes on every view in the hierarchy below the sceneRoot
+ * of the Scene being transitioned into. Setting targetIDs constrains
+ * the Transition to only listen for, and act on, views with these IDs.
+ * Views with different IDs, or no IDs whatsoever, will be ignored.
+ *
+ * @see View#getId()
+ * @param targetIds A list of IDs which specify the set of Views on which
+ * the Transition will act.
+ * @return Transition The Transition on which the targetIds have been set.
+ * Returning the same object makes it easier to chain calls during
+ * construction, such as
+ * <code>transitionGroup.addTransitions(new Fade()).setTargetIds(someId);</code>
+ */
+ public Transition setTargetIds(int... targetIds) {
+ int numTargets = targetIds.length;
+ mTargetIds = new int[numTargets];
+ System.arraycopy(targetIds, 0, mTargetIds, 0, numTargets);
+ return this;
+ }
+
+ /**
+ * Sets the target view instances that this Transition is interested in
+ * animating. By default, there are no targets, and a Transition will
+ * listen for changes on every view in the hierarchy below the sceneRoot
+ * of the Scene being transitioned into. Setting targets constrains
+ * the Transition to only listen for, and act on, these views.
+ * All other views will be ignored.
+ *
+ * <p>The target list is like the {@link #setTargetIds(int...) targetId}
+ * list except this list specifies the actual View instances, not the ids
+ * of the views. This is an important distinction when scene changes involve
+ * view hierarchies which have been inflated separately; different views may
+ * share the same id but not actually be the same instance. If the transition
+ * should treat those views as the same, then seTargetIds() should be used
+ * instead of setTargets(). If, on the other hand, scene changes involve
+ * changes all within the same view hierarchy, among views which do not
+ * necessary have ids set on them, then the target list may be more
+ * convenient.</p>
+ *
+ * @see #setTargetIds(int...)
+ * @param targets A list of Views on which the Transition will act.
+ * @return Transition The Transition on which the targets have been set.
+ * Returning the same object makes it easier to chain calls during
+ * construction, such as
+ * <code>transitionGroup.addTransitions(new Fade()).setTargets(someView);</code>
+ */
+ public Transition setTargets(View... targets) {
+ int numTargets = targets.length;
+ mTargets = new View[numTargets];
+ System.arraycopy(targets, 0, mTargets, 0, numTargets);
+ return this;
+ }
+
+ /**
+ * Returns the array of target IDs that this transition limits itself to
+ * tracking and animating. If the array is null for both this method and
+ * {@link #getTargets()}, then this transition is
+ * not limited to specific views, and will handle changes to any views
+ * in the hierarchy of a scene change.
+ *
+ * @return the list of target IDs
+ */
+ public int[] getTargetIds() {
+ return mTargetIds;
+ }
+
+ /**
+ * Returns the array of target views that this transition limits itself to
+ * tracking and animating. If the array is null for both this method and
+ * {@link #getTargetIds()}, then this transition is
+ * not limited to specific views, and will handle changes to any views
+ * in the hierarchy of a scene change.
+ *
+ * @return the list of target views
+ */
+ public View[] getTargets() {
+ return mTargets;
+ }
+
+ /**
+ * Recursive method that captures values for the given view and the
+ * hierarchy underneath it.
+ * @param sceneRoot The root of the view hierarchy being captured
+ * @param start true if this capture is happening before the scene change,
+ * false otherwise
+ */
+ void captureValues(ViewGroup sceneRoot, boolean start) {
+ if (mTargetIds != null && mTargetIds.length > 0 ||
+ mTargets != null && mTargets.length > 0) {
+ if (mTargetIds != null) {
+ for (int i = 0; i < mTargetIds.length; ++i) {
+ int id = mTargetIds[i];
+ View view = sceneRoot.findViewById(id);
+ if (view != null) {
+ TransitionValues values = new TransitionValues();
+ values.view = view;
+ captureValues(values, start);
+ if (start) {
+ mStartValues.put(view, values);
+ mStartIdValues.put(id, values);
+ } else {
+ mEndValues.put(view, values);
+ mEndIdValues.put(id, values);
+ }
+ }
+ }
+ }
+ if (mTargets != null) {
+ for (int i = 0; i < mTargets.length; ++i) {
+ View view = mTargets[i];
+ if (view != null) {
+ TransitionValues values = new TransitionValues();
+ values.view = view;
+ captureValues(values, start);
+ if (start) {
+ mStartValues.put(view, values);
+ } else {
+ mEndValues.put(view, values);
+ }
+ }
+ }
+ }
+ } else {
+ captureHierarchy(sceneRoot, start);
+ }
+ }
+
+ /**
+ * Recursive method which captures values for an entire view hierarchy,
+ * starting at some root view. Transitions without targetIDs will use this
+ * method to capture values for all possible views.
+ *
+ * @param view The view for which to capture values. Children of this View
+ * will also be captured, recursively down to the leaf nodes.
+ * @param start true if values are being captured in the start scene, false
+ * otherwise.
+ */
+ private void captureHierarchy(View view, boolean start) {
+ if (view == null) {
+ return;
+ }
+ boolean isListViewItem = false;
+ if (view.getParent() instanceof ListView) {
+ isListViewItem = true;
+ }
+ if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
+ // ignore listview children unless we can track them with stable IDs
+ return;
+ }
+ long id;
+ if (!isListViewItem) {
+ id = view.getId();
+ } else {
+ ListView listview = (ListView) view.getParent();
+ int position = listview.getPositionForView(view);
+ id = listview.getItemIdAtPosition(position);
+ view.setHasTransientState(true);
+ }
+ TransitionValues values = new TransitionValues();
+ values.view = view;
+ captureValues(values, start);
+ if (start) {
+ if (!isListViewItem) {
+ mStartValues.put(view, values);
+ mStartIdValues.put((int) id, values);
+ } else {
+ mStartItemIdValues.put(id, values);
+ }
+ } else {
+ if (!isListViewItem) {
+ mEndValues.put(view, values);
+ mEndIdValues.put((int) id, values);
+ } else {
+ mEndItemIdValues.put(id, values);
+ }
+ }
+ if (view instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) view;
+ for (int i = 0; i < parent.getChildCount(); ++i) {
+ captureHierarchy(parent.getChildAt(i), start);
+ }
+ }
+ }
+
+ /**
+ * Called by TransitionManager to play the transition. This calls
+ * prePlay() and then play() with the full set of per-view
+ * transitionValues objects
+ */
+ void play(ViewGroup sceneRoot) {
+ // prePlay() must be called on entire transition hierarchy and set of views
+ // before calling play() on anything; every transition needs a chance to set up
+ // target views appropriately before transitions begin running
+ prePlay(sceneRoot, mStartValues, mStartIdValues, mStartItemIdValues,
+ mEndValues, mEndIdValues, mEndItemIdValues);
+ play(sceneRoot, mStartValues, mStartIdValues, mStartItemIdValues,
+ mEndValues, mEndIdValues, mEndItemIdValues);
+ }
+
+ /**
+ * This is a utility method used by subclasses to handle standard parts of
+ * setting up and running an Animator: it sets the {@link #getDuration()
+ * duration} and the {@link #getStartDelay() startDelay}, starts the
+ * animation, and, when the animator ends, calls {@link #endTransition()}.
+ *
+ * @param animator The Animator to be run during this transition.
+ *
+ * @hide
+ */
+ protected void animate(Animator animator) {
+ // TODO: maybe pass auto-end as a boolean parameter?
+ if (animator == null) {
+ endTransition();
+ } else {
+ if (getDuration() >= 0) {
+ animator.setDuration(getDuration());
+ }
+ if (getStartDelay() >= 0) {
+ animator.setStartDelay(getStartDelay());
+ }
+ if (getInterpolator() != null) {
+ animator.setInterpolator(getInterpolator());
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelTransition();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endTransition();
+ animation.removeListener(this);
+ }
+ });
+ animator.start();
+ }
+ }
+
+ /**
+ * Subclasses may override to receive notice of when the transition starts.
+ * This is equivalent to listening for the
+ * {@link TransitionListener#onTransitionStart(Transition)} callback.
+ */
+ protected void onTransitionStart() {
+ }
+
+ /**
+ * Subclasses may override to receive notice of when the transition is
+ * canceled. This is equivalent to listening for the
+ * {@link TransitionListener#onTransitionCancel(Transition)} callback.
+ */
+ protected void onTransitionCancel() {
+ }
+
+ /**
+ * Subclasses may override to receive notice of when the transition ends.
+ * This is equivalent to listening for the
+ * {@link TransitionListener#onTransitionEnd(Transition)} callback.
+ */
+ protected void onTransitionEnd() {
+ }
+
+ /**
+ * This method is called automatically by the transition and
+ * TransitionGroup classes prior to a Transition subclass starting;
+ * subclasses should not need to call it directly.
+ *
+ * @hide
+ */
+ protected void startTransition() {
+ if (mNumInstances == 0) {
+ onTransitionStart();
+ if (mListeners != null && mListeners.size() > 0) {
+ ArrayList<TransitionListener> tmpListeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onTransitionStart(this);
+ }
+ }
+ }
+ mNumInstances++;
+ }
+
+ /**
+ * This method is called automatically by the Transition and
+ * TransitionGroup classes when a transition finishes, either because
+ * a transition did nothing (returned a null Animator from
+ * {@link Transition#play(ViewGroup, TransitionValues,
+ * TransitionValues)}) or because the transition returned a valid
+ * Animator and endTransition() was called in the onAnimationEnd()
+ * callback of the AnimatorListener.
+ *
+ * @hide
+ */
+ protected void endTransition() {
+ --mNumInstances;
+ if (mNumInstances == 0) {
+ onTransitionEnd();
+ if (mListeners != null && mListeners.size() > 0) {
+ ArrayList<TransitionListener> tmpListeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onTransitionEnd(this);
+ }
+ }
+ for (int i = 0; i < mStartItemIdValues.size(); ++i) {
+ TransitionValues tv = mStartItemIdValues.valueAt(i);
+ View v = tv.view;
+ if (v.hasTransientState()) {
+ v.setHasTransientState(false);
+ }
+ }
+ for (int i = 0; i < mEndItemIdValues.size(); ++i) {
+ TransitionValues tv = mEndItemIdValues.valueAt(i);
+ View v = tv.view;
+ if (v.hasTransientState()) {
+ v.setHasTransientState(false);
+ }
+ }
+ mStartValues.clear();
+ mStartIdValues.clear();
+ mStartItemIdValues.clear();
+ mEndValues.clear();
+ mEndIdValues.clear();
+ mEndItemIdValues.clear();
+ }
+ }
+
+ /**
+ * This method cancels a transition that is currently running.
+ * Implementation TBD.
+ */
+ protected void cancelTransition() {
+ // TODO: how does this work with instances?
+ // TODO: this doesn't actually do *anything* yet
+ onTransitionCancel();
+ if (mListeners != null && mListeners.size() > 0) {
+ ArrayList<TransitionListener> tmpListeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onTransitionCancel(this);
+ }
+ }
+ }
+
+ /**
+ * Adds a listener to the set of listeners that are sent events through the
+ * life of an animation, such as start, repeat, and end.
+ *
+ * @param listener the listener to be added to the current set of listeners
+ * for this animation.
+ */
+ public void addListener(TransitionListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<TransitionListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener from the set listening to this animation.
+ *
+ * @param listener the listener to be removed from the current set of
+ * listeners for this transition.
+ */
+ public void removeListener(TransitionListener listener) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ mListeners = null;
+ }
+ }
+
+ /**
+ * Gets the set of {@link TransitionListener} objects that are currently
+ * listening for events on this <code>Transition</code> object.
+ *
+ * @return ArrayList<TransitionListener> The set of listeners.
+ */
+ public ArrayList<TransitionListener> getListeners() {
+ return mListeners;
+ }
+
+ @Override
+ public String toString() {
+ return toString("");
+ }
+
+ String toString(String indent) {
+ String result = indent + getClass().getSimpleName() + "@" +
+ Integer.toHexString(hashCode()) + ": ";
+ result += "dur(" + mDuration + ") ";
+ result += "dly(" + mStartDelay + ") ";
+ result += "interp(" + mInterpolator + ") ";
+ result += "tgts(";
+ if (mTargetIds != null) {
+ for (int i = 0; i < mTargetIds.length; ++i) {
+ if (i > 0) {
+ result += ", ";
+ }
+ result += mTargetIds[i];
+ }
+ }
+ if (mTargets != null) {
+ for (int i = 0; i < mTargets.length; ++i) {
+ if (i > 0) {
+ result += ", ";
+ }
+ result += mTargets[i];
+ }
+ }
+ result += ")";
+ return result;
+ }
+
+ /**
+ * A transition listener receives notifications from a transition.
+ * Notifications indicate transition lifecycle events: when the transition
+ * begins, ends, or is canceled.
+ */
+ public static interface TransitionListener {
+ /**
+ * Notification about the start of the transition.
+ *
+ * @param transition The started transition.
+ */
+ void onTransitionStart(Transition transition);
+
+ /**
+ * Notification about the end of the transition. Canceled transitions
+ * will always notify listeners of both the cancellation and end
+ * events. That is, {@link #onTransitionEnd()} is always called,
+ * regardless of whether the transition was canceled or played
+ * through to completion.
+ *
+ * @param transition The transition which reached its end.
+ */
+ void onTransitionEnd(Transition transition);
+
+ /**
+ * Notification about the cancellation of the transition.
+ *
+ * @param transition The transition which was canceled.
+ */
+ void onTransitionCancel(Transition transition);
+ }
+
+ /**
+ * Utility adapter class to avoid having to override all three methods
+ * whenever someone just wants to listen for a single event.
+ *
+ * @hide
+ * */
+ public static class TransitionListenerAdapter implements TransitionListener {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ }
+ }
+
+}
diff --git a/core/java/android/view/transition/TransitionGroup.java b/core/java/android/view/transition/TransitionGroup.java
new file mode 100644
index 0000000..363872a
--- /dev/null
+++ b/core/java/android/view/transition/TransitionGroup.java
@@ -0,0 +1,292 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.util.AndroidRuntimeException;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A TransitionGroup is a parent of child transitions (including other
+ * TransitionGroups). Using TransitionGroups enables more complex
+ * choreography of transitions, where some groups play {@link #TOGETHER} and
+ * others play {@link #SEQUENTIALLY}. For example, {@link AutoTransition}
+ * uses a TransitionGroup to sequentially play a Fade(Fade.OUT), followed by
+ * a {@link Move}, followed by a Fade(Fade.OUT) transition.
+ */
+public class TransitionGroup extends Transition {
+
+ ArrayList<Transition> mTransitions = new ArrayList<Transition>();
+ private boolean mPlayTogether = true;
+ int mCurrentListeners;
+ boolean mStarted = false;
+
+ /**
+ * A flag used to indicate that the child transitions of this group
+ * should all start at the same time.
+ */
+ public static final int TOGETHER = 0;
+ /**
+ * A flag used to indicate that the child transitions of this group should
+ * play in sequence; when one child transition ends, the next child
+ * transition begins. Note that a transition does not end until all
+ * instances of it (which are playing on all applicable targets of the
+ * transition) end.
+ */
+ public static final int SEQUENTIALLY = 1;
+
+ /**
+ * Constructs an empty transition group. Add child transitions to the
+ * group by calling to {@link #addTransitions(Transition...)} )}. By default,
+ * child transitions will play {@link #TOGETHER}.
+ */
+ public TransitionGroup() {
+ }
+
+ /**
+ * Constructs an empty transition group with the specified ordering.
+ *
+ * @param ordering {@link #TOGETHER} to start this group's child
+ * transitions together, {@link #SEQUENTIALLY} to play the child
+ * transitions in sequence.
+ * @see #setOrdering(int)
+ */
+ public TransitionGroup(int ordering) {
+ setOrdering(ordering);
+ }
+
+ /**
+ * Sets the play order of this group's child transitions.
+ *
+ * @param ordering {@link #TOGETHER} to start this group's child
+ * transitions together, {@link #SEQUENTIALLY} to play the child
+ * transitions in sequence.
+ */
+ public void setOrdering(int ordering) {
+ switch (ordering) {
+ case SEQUENTIALLY:
+ mPlayTogether = false;
+ break;
+ case TOGETHER:
+ mPlayTogether = true;
+ break;
+ default:
+ throw new AndroidRuntimeException("Invalid parameter for TransitionGroup " +
+ "ordering: " + ordering);
+ }
+ }
+
+ /**
+ * Adds child transitions to this group. The order of the child transitions
+ * passed in determines the order in which they are started.
+ *
+ * @param transitions A list of child transition to be added to this group.
+ */
+ public void addTransitions(Transition... transitions) {
+ if (transitions != null) {
+ int numTransitions = transitions.length;
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.add(transitions[i]);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified child transition from this group.
+ *
+ * @param transition The transition to be removed.
+ */
+ public void removeTransition(Transition transition) {
+ mTransitions.remove(transition);
+ }
+
+ /**
+ * Sets up listeners for each of the child transitions. This is used to
+ * determine when this transition group is finished (all child transitions
+ * must finish first).
+ */
+ private void setupStartEndListeners() {
+ for (Transition childTransition : mTransitions) {
+ childTransition.addListener(mListener);
+ }
+ mCurrentListeners = mTransitions.size();
+ }
+
+ /**
+ * This listener is used to detect when all child transitions are done, at
+ * which point this transition group is also done.
+ */
+ private TransitionListener mListener = new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ if (!mStarted) {
+ startTransition();
+ mStarted = true;
+ }
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ --mCurrentListeners;
+ if (mCurrentListeners == 0) {
+ // All child trans
+ mStarted = false;
+ endTransition();
+ }
+ transition.removeListener(this);
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void prePlay(ViewGroup sceneRoot,
+ HashMap<View, TransitionValues> startValues,
+ SparseArray<TransitionValues> startIdValues,
+ LongSparseArray<TransitionValues> startItemIdValues,
+ HashMap<View, TransitionValues> endValues,
+ SparseArray<TransitionValues> endIdValues,
+ LongSparseArray<TransitionValues> endItemIdValues) {
+ for (Transition childTransition : mTransitions) {
+ childTransition.prePlay(sceneRoot, startValues, startIdValues, startItemIdValues,
+ endValues, endIdValues, endItemIdValues);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void play(ViewGroup sceneRoot,
+ final HashMap<View, TransitionValues> startValues,
+ final SparseArray<TransitionValues> startIdValues,
+ final LongSparseArray<TransitionValues> startItemIdValues,
+ final HashMap<View, TransitionValues> endValues,
+ final SparseArray<TransitionValues> endIdValues,
+ final LongSparseArray<TransitionValues> endItemIdValues) {
+ setupStartEndListeners();
+ final ViewGroup finalSceneRoot = sceneRoot;
+ if (!mPlayTogether) {
+ // Setup sequence with listeners
+ // TODO: Need to add listeners in such a way that we can remove them later if canceled
+ for (int i = 1; i < mTransitions.size(); ++i) {
+ Transition previousTransition = mTransitions.get(i - 1);
+ final Transition nextTransition = mTransitions.get(i);
+ previousTransition.addListener(new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ nextTransition.play(finalSceneRoot,
+ startValues, startIdValues, startItemIdValues,
+ endValues, endIdValues, endItemIdValues);
+ transition.removeListener(this);
+ }
+ });
+ }
+ Transition firstTransition = mTransitions.get(0);
+ if (firstTransition != null) {
+ firstTransition.play(finalSceneRoot, startValues, startIdValues, startItemIdValues,
+ endValues, endIdValues, endItemIdValues);
+ }
+ } else {
+ for (Transition childTransition : mTransitions) {
+ childTransition.play(finalSceneRoot, startValues, startIdValues, startItemIdValues,
+ endValues, endIdValues, endItemIdValues);
+ }
+ }
+ }
+
+ @Override
+ protected Animator play(ViewGroup sceneRoot,
+ TransitionValues startValues, TransitionValues endValues) {
+ final View view = (endValues != null) ? endValues.view :
+ (startValues != null) ? startValues.view : null;
+ final int targetId = (view != null) ? view.getId() : -1;
+ // TODO: not sure this is a valid check - what about auto-targets? No need for ids.
+ if (targetId < 0) {
+ return null;
+ }
+ setupStartEndListeners();
+ if (!mPlayTogether) {
+ final ViewGroup finalSceneRoot = sceneRoot;
+ final TransitionValues finalStartValues = startValues;
+ final TransitionValues finalEndValues = endValues;
+ // Setup sequence with listeners
+ // TODO: Need to add listeners in such a way that we can remove them later if canceled
+ for (int i = 1; i < mTransitions.size(); ++i) {
+ Transition previousTransition = mTransitions.get(i - 1);
+ final Transition nextTransition = mTransitions.get(i);
+ previousTransition.addListener(new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ nextTransition.startTransition();
+ if (nextTransition.isValidTarget(view, targetId)) {
+ animate(nextTransition.play(finalSceneRoot, finalStartValues,
+ finalEndValues));
+ } else {
+ nextTransition.endTransition();
+ }
+ }
+ });
+ }
+ Transition firstTransition = mTransitions.get(0);
+ if (firstTransition != null) {
+ firstTransition.startTransition();
+ if (firstTransition.isValidTarget(view, targetId)) {
+ animate(firstTransition.play(finalSceneRoot, finalStartValues, finalEndValues));
+ } else {
+ firstTransition.endTransition();
+ }
+ }
+ } else {
+ for (Transition childTransition : mTransitions) {
+ childTransition.startTransition();
+ if (childTransition.isValidTarget(view, targetId)) {
+ animate(childTransition.play(sceneRoot, startValues, endValues));
+ } else {
+ childTransition.endTransition();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void captureValues(TransitionValues transitionValues, boolean start) {
+ int targetId = transitionValues.view.getId();
+ for (Transition childTransition : mTransitions) {
+ if (childTransition.isValidTarget(transitionValues.view, targetId)) {
+ childTransition.captureValues(transitionValues, start);
+ }
+ }
+ }
+
+ @Override
+ String toString(String indent) {
+ String result = super.toString(indent);
+ for (int i = 0; i < mTransitions.size(); ++i) {
+ result += "\n" + mTransitions.get(i).toString(indent + " ");
+ }
+ return result;
+ }
+
+}
diff --git a/core/java/android/view/transition/TransitionInflater.java b/core/java/android/view/transition/TransitionInflater.java
new file mode 100644
index 0000000..a5f5836
--- /dev/null
+++ b/core/java/android/view/transition/TransitionInflater.java
@@ -0,0 +1,392 @@
+/*
+ * 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.transition;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.Xml;
+import android.view.InflateException;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class inflates scenes and transitions from resource files.
+ */
+public class TransitionInflater {
+
+ // We only need one inflater for any given context. Also, this allows us to associate
+ // ids with unique instances per-Context, used to avoid re-inflating
+ // already-inflated resources into new/different instances
+ private static final HashMap<Context, TransitionInflater> sInflaterMap =
+ new HashMap<Context, TransitionInflater>();
+
+ private Context mContext;
+ // TODO: do we need id maps for transitions and transitionMgrs as well?
+ SparseArray<Scene> mScenes = new SparseArray<Scene>();
+
+ private TransitionInflater(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Obtains the TransitionInflater from the given context.
+ */
+ public static TransitionInflater from(Context context) {
+ TransitionInflater inflater = sInflaterMap.get(context);
+ if (inflater != null) {
+ return inflater;
+ }
+ inflater = new TransitionInflater(context);
+ sInflaterMap.put(context, inflater);
+ return inflater;
+ }
+
+ /**
+ * Loads a {@link Transition} object from a resource
+ *
+ * @param resource The resource id of the transition to load
+ * @return The loaded Transition object
+ * @throws android.content.res.Resources.NotFoundException when the
+ * transition cannot be loaded
+ */
+ public Transition inflateTransition(int resource) {
+ XmlResourceParser parser = mContext.getResources().getXml(resource);
+ try {
+ return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
+ } catch (XmlPullParserException e) {
+ InflateException ex = new InflateException(e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } catch (IOException e) {
+ InflateException ex = new InflateException(
+ parser.getPositionDescription()
+ + ": " + e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Loads a {@link TransitionManager} object from a resource
+ *
+ *
+ *
+ * @param resource The resource id of the transition manager to load
+ * @return The loaded TransitionManager object
+ * @throws android.content.res.Resources.NotFoundException when the
+ * transition manager cannot be loaded
+ */
+ public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
+ XmlResourceParser parser = mContext.getResources().getXml(resource);
+ try {
+ return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
+ } catch (XmlPullParserException e) {
+ InflateException ex = new InflateException(e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } catch (IOException e) {
+ InflateException ex = new InflateException(
+ parser.getPositionDescription()
+ + ": " + e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Loads a {@link Scene} object from a resource
+ *
+ * @param resource The resource id of the scene to load
+ * @return The loaded Scene object
+ * @throws android.content.res.Resources.NotFoundException when the scene
+ * cannot be loaded
+ */
+ public Scene inflateScene(int resource, ViewGroup parent) {
+ Scene scene = mScenes.get(resource);
+ if (scene != null) {
+ return scene;
+ }
+ XmlResourceParser parser = mContext.getResources().getXml(resource);
+ try {
+ scene = createSceneFromXml(parser, Xml.asAttributeSet(parser), parent);
+ mScenes.put(resource, scene);
+ return scene;
+ } catch (XmlPullParserException e) {
+ InflateException ex = new InflateException(e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } catch (IOException e) {
+ InflateException ex = new InflateException(
+ parser.getPositionDescription()
+ + ": " + e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } finally {
+ parser.close();
+ }
+ }
+
+
+ //
+ // Transition loading
+ //
+
+ private Transition createTransitionFromXml(XmlPullParser parser,
+ AttributeSet attrs, TransitionGroup transitionGroup)
+ throws XmlPullParserException, IOException {
+
+ Transition transition = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ boolean newTransition = false;
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ if ("fade".equals(name)) {
+ transition = new Fade();
+ newTransition = true;
+ } else if ("move".equals(name)) {
+ transition = new Move();
+ newTransition = true;
+ } else if ("slide".equals(name)) {
+ transition = new Slide();
+ newTransition = true;
+ } else if ("autoTransition".equals(name)) {
+ transition = new AutoTransition();
+ newTransition = true;
+ } else if ("recolor".equals(name)) {
+ transition = new Recolor();
+ newTransition = true;
+ } else if ("transitionGroup".equals(name)) {
+ transition = new TransitionGroup();
+ createTransitionFromXml(parser, attrs, ((TransitionGroup) transition));
+ newTransition = true;
+ } else if ("targets".equals(name)) {
+ if (parser.getDepth() - 1 > depth && transition != null) {
+ // We're inside the child tag - add targets to the child
+ getTargetIDs(parser, attrs, transition);
+ } else if (parser.getDepth() - 1 == depth && transitionGroup != null) {
+ // add targets to the group
+ getTargetIDs(parser, attrs, transitionGroup);
+ }
+ }
+ if (transition != null || "targets".equals(name)) {
+ if (newTransition) {
+ loadTransition(transition, attrs);
+ if (transitionGroup != null) {
+ transitionGroup.addTransitions(transition);
+ }
+ }
+ } else {
+ throw new RuntimeException("Unknown scene name: " + parser.getName());
+ }
+ }
+
+ return transition;
+ }
+
+ private void getTargetIDs(XmlPullParser parser,
+ AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ ArrayList<Integer> targetIds = new ArrayList<Integer>();
+ while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ if (name.equals("target")) {
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Transition);
+ int id = a.getResourceId(com.android.internal.R.styleable.Transition_targetID, -1);
+ if (id >= 0) {
+ targetIds.add(id);
+ }
+ } else {
+ throw new RuntimeException("Unknown scene name: " + parser.getName());
+ }
+ }
+ int numTargets = targetIds.size();
+ if (numTargets > 0) {
+ int[] targetsArray = new int[numTargets];
+ for (int i = 0; i < targetIds.size(); ++i) {
+ targetsArray[i] = targetIds.get(i);
+ }
+ transition.setTargetIds(targetsArray);
+ }
+ }
+
+ private Transition loadTransition(Transition transition, AttributeSet attrs)
+ throws Resources.NotFoundException {
+
+ TypedArray a =
+ mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
+ long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
+ if (duration >= 0) {
+ transition.setDuration(duration);
+ }
+ long startOffset = a.getInt(com.android.internal.R.styleable.Transition_startOffset, -1);
+ if (startOffset > 0) {
+ transition.setStartDelay(startOffset);
+ }
+ final int resID =
+ a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+ if (resID > 0) {
+ transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
+ }
+ a.recycle();
+ return transition;
+ }
+
+ //
+ // TransitionManager loading
+ //
+
+ private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
+ AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+ TransitionManager transitionManager = null;
+
+ while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ if (name.equals("transitionManager")) {
+ transitionManager = new TransitionManager();
+ } else if (name.equals("transition") && (transitionManager != null)) {
+ loadTransition(attrs, sceneRoot, transitionManager);
+ } else {
+ throw new RuntimeException("Unknown scene name: " + parser.getName());
+ }
+ }
+ return transitionManager;
+ }
+
+ private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
+ TransitionManager transitionManager)
+ throws Resources.NotFoundException {
+
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.TransitionManager);
+ int transitionId = attrs.getAttributeResourceValue(
+ com.android.internal.R.styleable.TransitionManager_transition, -1);
+ Scene fromScene = null, toScene = null;
+ int fromId = attrs.getAttributeResourceValue(
+ com.android.internal.R.styleable.TransitionManager_fromScene, -1);
+ if (fromId >= 0) fromScene = inflateScene(fromId, sceneRoot);
+ int toId = attrs.getAttributeResourceValue(
+ com.android.internal.R.styleable.TransitionManager_toScene, -1);
+ if (toId >= 0) toScene = inflateScene(toId, sceneRoot);
+ if (transitionId >= 0) {
+ Transition transition = inflateTransition(transitionId);
+ if (transition != null) {
+ if (fromScene != null) {
+ if (toScene == null){
+ throw new RuntimeException("No matching toScene for given fromScene " +
+ "for transition ID " + transitionId);
+ } else {
+ transitionManager.setTransition(fromScene, toScene, transition);
+ }
+ } else if (toId >= 0) {
+ transitionManager.setTransition(toScene, transition);
+ }
+ }
+ }
+ a.recycle();
+ }
+
+ //
+ // Scene loading
+ //
+
+ private Scene createSceneFromXml(XmlPullParser parser, AttributeSet attrs, ViewGroup parent)
+ throws XmlPullParserException, IOException {
+ Scene scene = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ if (name.equals("scene")) {
+ scene = loadScene(attrs, parent);
+ } else {
+ throw new RuntimeException("Unknown scene name: " + parser.getName());
+ }
+ }
+
+ return scene;
+ }
+
+ private Scene loadScene(AttributeSet attrs, ViewGroup parent)
+ throws Resources.NotFoundException {
+
+ Scene scene;
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Scene);
+ int layoutId = a.getResourceId(com.android.internal.R.styleable.Scene_layout, -1);
+ if (layoutId >= 0) {
+ scene = new Scene(parent, layoutId, mContext);
+ } else {
+ scene = new Scene(parent);
+ }
+ a.recycle();
+ return scene;
+ }
+}
diff --git a/core/java/android/view/transition/TransitionManager.java b/core/java/android/view/transition/TransitionManager.java
new file mode 100644
index 0000000..5a1991c
--- /dev/null
+++ b/core/java/android/view/transition/TransitionManager.java
@@ -0,0 +1,261 @@
+/*
+ * 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.transition;
+
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.HashMap;
+
+/**
+ * This class manages the set of transitions that fire when there is a
+ * change of {@link Scene}. To use the manager, add scenes along with
+ * transition objects with calls to {@link #setTransition(Scene, Transition)}
+ * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific
+ * transitions for scene changes is not required; by default, a Scene change
+ * will use {@link AutoTransition} to do something reasonable for most
+ * situations. Specifying other transitions for particular scene changes is
+ * only necessary if the application wants different transition behavior
+ * in these situations.
+ */
+public class TransitionManager {
+ // TODO: how to handle enter/exit?
+
+ private static final Transition sDefaultTransition = new AutoTransition();
+ private Transition mDefaultTransition = new AutoTransition();
+
+ HashMap<Scene, Transition> mSceneTransitions = new HashMap<Scene, Transition>();
+ HashMap<Scene, HashMap<Scene, Transition>> mScenePairTransitions =
+ new HashMap<Scene, HashMap<Scene, Transition>>();
+
+ /**
+ * Sets the transition to be used for any scene change for which no
+ * other transition is explicitly set. The initial value is
+ * an {@link AutoTransition} instance.
+ *
+ * @param transition The default transition to be used for scene changes.
+ */
+ public void setDefaultTransition(Transition transition) {
+ mDefaultTransition = transition;
+ }
+
+ /**
+ * Gets the current default transition. The initial value is an {@link
+ * AutoTransition} instance.
+ *
+ * @return The current default transition.
+ * @see #setDefaultTransition(Transition)
+ */
+ public Transition getDefaultTransition() {
+ return mDefaultTransition;
+ }
+
+ /**
+ * Sets a specific transition to occur when the given scene is entered.
+ *
+ * @param scene The scene which, when applied, will cause the given
+ * transition to run.
+ * @param transition The transition that will play when the given scene is
+ * entered. A value of null will result in the default behavior of
+ * using {@link AutoTransition}.
+ */
+ public void setTransition(Scene scene, Transition transition) {
+ mSceneTransitions.put(scene, transition);
+ }
+
+ /**
+ * Sets a specific transition to occur when the given pair of scenes is
+ * exited/entered.
+ *
+ * @param fromScene The scene being exited when the given transition will
+ * be run
+ * @param toScene The scene being entered when the given transition will
+ * be run
+ * @param transition The transition that will play when the given scene is
+ * entered. A value of null will result in the default behavior of
+ * using {@link AutoTransition}.
+ */
+ public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
+ HashMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
+ if (sceneTransitionMap == null) {
+ sceneTransitionMap = new HashMap<Scene, Transition>();
+ mScenePairTransitions.put(toScene, sceneTransitionMap);
+ }
+ sceneTransitionMap.put(fromScene, transition);
+ }
+
+ /**
+ * Returns the Transition for the given scene being entered. The result
+ * depends not only on the given scene, but also the scene which the
+ * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
+ *
+ * @param scene The scene being entered
+ * @return The Transition to be used for the given scene change. If no
+ * Transition was specified for this scene change, {@link AutoTransition}
+ * will be used instead.
+ */
+ private Transition getTransition(Scene scene) {
+ Transition transition = null;
+ ViewGroup sceneRoot = scene.getSceneRoot();
+ if (sceneRoot != null) {
+ // TODO: cached in Scene instead? long-term, cache in View itself
+ Scene currScene = sceneRoot.getCurrentScene();
+ if (currScene != null) {
+ HashMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
+ if (sceneTransitionMap != null) {
+ transition = sceneTransitionMap.get(currScene);
+ if (transition != null) {
+ return transition;
+ }
+ }
+ }
+ }
+ transition = mSceneTransitions.get(scene);
+ return (transition != null) ? transition : new AutoTransition();
+ }
+
+ /**
+ * This is where all of the work of a transition/scene-change is
+ * orchestrated. This method captures the start values for the given
+ * transition, exits the current Scene, enters the new scene, captures
+ * the end values for the transition, and finally plays the
+ * resulting values-populated transition.
+ *
+ * @param scene The scene being entered
+ * @param transition The transition to play for this scene change
+ */
+ private static void changeScene(Scene scene, final Transition transition) {
+
+ final ViewGroup sceneRoot = scene.getSceneRoot();
+
+ // Capture current values
+ if (transition != null) {
+ transition.captureValues(sceneRoot, true);
+ }
+
+ // Notify previous scene that it is being exited
+ Scene previousScene = sceneRoot.getCurrentScene();
+ if (previousScene != null) {
+ previousScene.exit();
+ }
+
+ scene.enter();
+
+ if (transition != null) {
+ final ViewTreeObserver observer = sceneRoot.getViewTreeObserver();
+ observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ transition.captureValues(sceneRoot, false);
+ transition.play(sceneRoot);
+ return true;
+ }
+ });
+ }
+ }
+
+ /**
+ * Change to the given scene, using the
+ * appropriate transition for this particular scene change
+ * (as specified to the TransitionManager, or the default
+ * if no such transition exists).
+ *
+ * @param scene The Scene to change to
+ */
+ public void transitionTo(Scene scene) {
+ // Auto transition if there is no transition declared for the Scene, but there is
+ // a root or parent view
+ changeScene(scene, getTransition(scene));
+
+ }
+
+ /**
+ * Static utility method to simply change to the given scene using
+ * the default transition for TransitionManager.
+ *
+ * @param scene The Scene to change to
+ */
+ public static void go(Scene scene) {
+ changeScene(scene, sDefaultTransition);
+ }
+
+ /**
+ * Static utility method to simply change to the given scene using
+ * the given transition.
+ *
+ * <p>Passing in <code>null</code> for the transition parameter will
+ * result in the scene changing without any transition running, and is
+ * equivalent to calling {@link Scene#exit()} on the scene root's
+ * {@link ViewGroup#getCurrentScene() current scene}, followed by
+ * {@link Scene#enter()} on the scene specified by the <code>scene</code>
+ * parameter.</p>
+ *
+ * @param scene The Scene to change to
+ * @param transition The transition to use for this scene change. A
+ * value of null causes the scene change to happen with no transition.
+ */
+ public static void go(Scene scene, Transition transition) {
+ changeScene(scene, transition);
+ }
+
+ /**
+ * Static utility method to simply change to a scene defined by the
+ * code in the given runnable, which will be executed after
+ * the current values have been captured for the transition.
+ * This is equivalent to creating a Scene and calling {@link
+ * Scene#setEnterAction(Runnable)} with the runnable, then calling
+ * {@link #go(Scene, Transition)}. The transition used will be the
+ * default provided by TransitionManager.
+ *
+ * @param sceneRoot The root of the View hierarchy used when this scene
+ * runs a transition automatically.
+ * @param action The runnable whose {@link Runnable#run() run()} method will
+ * be called.
+ */
+ public static void go(ViewGroup sceneRoot, Runnable action) {
+ Scene scene = new Scene(sceneRoot);
+ scene.setEnterAction(action);
+ changeScene(scene, sDefaultTransition);
+ }
+
+ /**
+ * Static utility method to simply change to a scene defined by the
+ * code in the given runnable, which will be executed after
+ * the current values have been captured for the transition.
+ * This is equivalent to creating a Scene and calling {@link
+ * Scene#setEnterAction(Runnable)} with the runnable, then calling
+ * {@link #go(Scene, Transition)}. The given transition will be
+ * used to animate the changes.
+ *
+ * <p>Passing in <code>null</code> for the transition parameter will
+ * result in the scene changing without any transition running, and is
+ * equivalent to calling {@link Scene#exit()} on the scene root's
+ * {@link ViewGroup#getCurrentScene() current scene}, followed by
+ * {@link Scene#enter()} on a new scene specified by the
+ * <code>action</code> parameter.</p>
+ *
+ * @param sceneRoot The root of the View hierarchy to run the transition on.
+ * @param action The runnable whose {@link Runnable#run() run()} method will
+ * be called.
+ * @param transition The transition to use for this change. A
+ * value of null causes the change to happen with no transition.
+ */
+ public static void go(ViewGroup sceneRoot, Runnable action, Transition transition) {
+ Scene scene = new Scene(sceneRoot);
+ scene.setEnterAction(action);
+ changeScene(scene, transition);
+ }
+}
diff --git a/core/java/android/view/transition/TransitionValues.java b/core/java/android/view/transition/TransitionValues.java
new file mode 100644
index 0000000..120ace8
--- /dev/null
+++ b/core/java/android/view/transition/TransitionValues.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.transition;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+
+/**
+ * Data structure which holds cached values for the transition.
+ * The view field is the target which all of the values pertain to.
+ * The values field is a hashmap which holds information for fields
+ * according to names selected by the transitions. These names should
+ * be unique to avoid clobbering values stored by other transitions,
+ * such as the convention project:transition_name:property_name. For
+ * example, the platform might store a property "alpha" in a transition
+ * "Fader" as "android:fader:alpha".
+ *
+ * <p>These values are cached during the
+ * {@link Transition#captureValues(TransitionValues, boolean)}
+ * capture} phases of a scene change, once when the start values are captured
+ * and again when the end values are captured. These start/end values are then
+ * passed into the transitions during the play phase of the scene change,
+ * for {@link Transition#prePlay(ViewGroup, TransitionValues, TransitionValues)} and
+ * for {@link Transition#play(ViewGroup, TransitionValues, TransitionValues)}.</p>
+ */
+public class TransitionValues {
+
+ /**
+ * The View with these values
+ */
+ public View view;
+
+ /**
+ * The set of values tracked by transitions for this scene
+ */
+ public final HashMap<String, Object> values = new HashMap<String, Object>();
+
+ @Override
+ public String toString() {
+ String returnValue = "TransitionValues@" + Integer.toHexString(hashCode()) + ":\n";
+ returnValue += " view = " + view + "\n";
+ returnValue += " values = " + values + "\n";
+ return returnValue;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/view/transition/Visibility.java b/core/java/android/view/transition/Visibility.java
new file mode 100644
index 0000000..a3e6e77
--- /dev/null
+++ b/core/java/android/view/transition/Visibility.java
@@ -0,0 +1,243 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes. Visibility is determined not just by the
+ * {@link View#setVisibility(int)} state of views, but also whether
+ * views exist in the current view hierarchy. The class is intended to be a
+ * utility for subclasses such as {@link Fade}, which use this visibility
+ * information to determine the specific animations to run when visibility
+ * changes occur. Subclasses should implement one or more of the methods
+ * {@link #preAppear(ViewGroup, View, int, View, int)},
+ * {@link #preDisappear(ViewGroup, View, int, View, int)},
+ * {@link #appear(ViewGroup, View, int, View, int)}, and
+ * {@link #disappear(ViewGroup, View, int, View, int)}.
+ */
+public abstract class Visibility extends Transition {
+
+ private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
+ private static final String PROPNAME_PARENT = "android:visibility:parent";
+
+ @Override
+ protected void captureValues(TransitionValues values, boolean start) {
+ int visibility = values.view.getVisibility();
+ values.values.put(PROPNAME_VISIBILITY, visibility);
+ values.values.put(PROPNAME_PARENT, values.view.getParent());
+ }
+
+ @Override
+ protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ boolean visibilityChange = false;
+ boolean fadeIn = false;
+ int startVisibility, endVisibility;
+ View startParent, endParent;
+ if (startValues != null) {
+ startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
+ startParent = (View) startValues.values.get(PROPNAME_PARENT);
+ } else {
+ startVisibility = -1;
+ startParent = null;
+ }
+ if (endValues != null) {
+ endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
+ endParent = (View) endValues.values.get(PROPNAME_PARENT);
+ } else {
+ endVisibility = -1;
+ endParent = null;
+ }
+ boolean existenceChange = false;
+ if (startValues != null && endValues != null) {
+ if (startVisibility == endVisibility && startParent == endParent) {
+ return false;
+ } else {
+ if (startVisibility != endVisibility) {
+ if (startVisibility == View.VISIBLE) {
+ fadeIn = false;
+ visibilityChange = true;
+ } else if (endVisibility == View.VISIBLE) {
+ fadeIn = true;
+ visibilityChange = true;
+ }
+ // no visibilityChange if going between INVISIBLE and GONE
+ } else if (startParent != endParent) {
+ existenceChange = true;
+ if (endParent == null) {
+ fadeIn = false;
+ visibilityChange = true;
+ } else if (startParent == null) {
+ fadeIn = true;
+ visibilityChange = true;
+ }
+ }
+ }
+ }
+ if (startValues == null) {
+ existenceChange = true;
+ fadeIn = true;
+ visibilityChange = true;
+ } else if (endValues == null) {
+ existenceChange = true;
+ fadeIn = false;
+ visibilityChange = true;
+ }
+ if (visibilityChange) {
+ if (fadeIn) {
+ return preAppear(sceneRoot, existenceChange ? null : startValues.view,
+ startVisibility, endValues.view, endVisibility);
+ } else {
+ return preDisappear(sceneRoot, startValues.view, startVisibility,
+ existenceChange ? null : endValues.view, endVisibility);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ boolean visibilityChange = false;
+ boolean fadeIn = false;
+ int startVisibility, endVisibility;
+ View startParent, endParent;
+ if (startValues != null) {
+ startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
+ startParent = (View) startValues.values.get(PROPNAME_PARENT);
+ } else {
+ startVisibility = -1;
+ startParent = null;
+ }
+ if (endValues != null) {
+ endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
+ endParent = (View) endValues.values.get(PROPNAME_PARENT);
+ } else {
+ endVisibility = -1;
+ endParent = null;
+ }
+ boolean existenceChange = false;
+ if (startValues != null && endValues != null) {
+ if (startVisibility == endVisibility && startParent == endParent) {
+ return null;
+ } else {
+ if (startVisibility != endVisibility) {
+ if (startVisibility == View.VISIBLE) {
+ fadeIn = false;
+ visibilityChange = true;
+ } else if (endVisibility == View.VISIBLE) {
+ fadeIn = true;
+ visibilityChange = true;
+ }
+ // no visibilityChange if going between INVISIBLE and GONE
+ } else if (startParent != endParent) {
+ existenceChange = true;
+ if (endParent == null) {
+ fadeIn = false;
+ visibilityChange = true;
+ } else if (startParent == null) {
+ fadeIn = true;
+ visibilityChange = true;
+ }
+ }
+ }
+ }
+ if (startValues == null) {
+ existenceChange = true;
+ fadeIn = true;
+ visibilityChange = true;
+ } else if (endValues == null) {
+ existenceChange = true;
+ fadeIn = false;
+ visibilityChange = true;
+ }
+ if (visibilityChange) {
+ if (fadeIn) {
+ return appear(sceneRoot, existenceChange ? null : startValues.view, startVisibility,
+ endValues.view, endVisibility);
+ } else {
+ return disappear(sceneRoot, startValues.view, startVisibility,
+ existenceChange ? null : endValues.view, endVisibility);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The default implementation of this method does nothing. Subclasses
+ * should override if they need to set up anything prior to the
+ * transition starting.
+ *
+ * @param sceneRoot
+ * @param startView
+ * @param startVisibility
+ * @param endView
+ * @param endVisibility
+ * @return
+ */
+ protected boolean preAppear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ return true;
+ }
+
+ /**
+ * The default implementation of this method does nothing. Subclasses
+ * should override if they need to set up anything prior to the
+ * transition starting.
+ * @param sceneRoot
+ * @param startView
+ * @param startVisibility
+ * @param endView
+ * @param endVisibility
+ * @return
+ */
+ protected boolean preDisappear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) {
+ return true;
+ }
+
+ /**
+ * The default implementation of this method does nothing. Subclasses
+ * should override if they need to do anything when target objects
+ * appear during the scene change.
+ * @param sceneRoot
+ * @param startView
+ * @param startVisibility
+ * @param endView
+ * @param endVisibility
+ */
+ protected Animator appear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) { return null; }
+
+ /**
+ * The default implementation of this method does nothing. Subclasses
+ * should override if they need to do anything when target objects
+ * disappear during the scene change.
+ * @param sceneRoot
+ * @param startView
+ * @param startVisibility
+ * @param endView
+ * @param endVisibility
+ */
+ protected Animator disappear(ViewGroup sceneRoot, View startView, int startVisibility,
+ View endView, int endVisibility) { return null; }
+
+}
diff --git a/core/java/android/view/transition/package.html b/core/java/android/view/transition/package.html
new file mode 100644
index 0000000..37dc0ec
--- /dev/null
+++ b/core/java/android/view/transition/package.html
@@ -0,0 +1,25 @@
+<html>
+<body>
+<p>The classes in this package enable "scenes & transitions" functionality for
+view hiearchies.</p>
+
+<p>A <b>Scene</b> is an encapsulation of the state of a view hiearchy,
+including the views in that hierarchy and the various values (layout-related
+and otherwise) that those views have. A scene be defined by a layout hierarchy
+directly or some code which sets up the scene dynamically as it is entered.</p>
+
+<p>A <b>Transition</b> is a mechanism to automatically animate changes that occur
+when a new scene is entered. Some transition capabilities are automatic. That
+is, entering a scene may cause animations to run which fade out views that
+go away, move and resize existing views that change, and fade in views that
+become visible. There are additional transitions that can animate other
+attributes, such as color changes, and which can optionally be specified
+to take place during particular scene changes. Finally, developers can
+define their own Transition subclasses which monitor particular property
+changes and which run custom animations when those properties change values.</p>
+
+<p><b>TransitionManager</b> is used to specify custom transitions for particular
+scene changes, and to cause scene changes with transitions to take place.</p>
+
+</body>
+</html>
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index bf66292..ba85c1a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1151,10 +1151,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
if (mChoiceMode != CHOICE_MODE_NONE) {
if (mCheckStates == null) {
- mCheckStates = new SparseBooleanArray();
+ mCheckStates = new SparseBooleanArray(0);
}
if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
- mCheckedIdStates = new LongSparseArray<Integer>();
+ mCheckedIdStates = new LongSparseArray<Integer>(0);
}
// Modal multi-choice mode only has choices when the mode is active. Clear them.
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
@@ -1615,10 +1615,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
+ @Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
+ @Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
@@ -1943,8 +1945,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
final int top = getChildAt(0).getTop();
- final float fadeLength = (float) getVerticalFadingEdgeLength();
- return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
+ final float fadeLength = getVerticalFadingEdgeLength();
+ return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
}
}
@@ -1961,9 +1963,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int bottom = getChildAt(count - 1).getBottom();
final int height = getHeight();
- final float fadeLength = (float) getVerticalFadingEdgeLength();
+ final float fadeLength = getVerticalFadingEdgeLength();
return bottom > height - mPaddingBottom ?
- (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
+ (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
}
}
@@ -2426,7 +2428,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @return True if the selector should be shown
*/
boolean shouldShowSelector() {
- return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
+ return (!isInTouchMode()) || touchModeDrawsInPressedState();
}
private void drawSelector(Canvas canvas) {
@@ -2764,13 +2766,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
public boolean sameWindow() {
- return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
+ return getWindowAttachCount() == mOriginalAttachCount;
}
}
private class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition;
+ @Override
public void run() {
// The data has changed since we posted this action in the event queue,
// bail out before bad things happen
@@ -2792,6 +2795,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private class CheckForLongPress extends WindowRunnnable implements Runnable {
+ @Override
public void run() {
final int motionPosition = mMotionPosition;
final View child = getChildAt(motionPosition - mFirstPosition);
@@ -2815,6 +2819,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
+ @Override
public void run() {
if (isPressed() && mSelectedPosition >= 0) {
int index = mSelectedPosition - mFirstPosition;
@@ -2989,6 +2994,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
final class CheckForTap implements Runnable {
+ @Override
public void run() {
if (mTouchMode == TOUCH_MODE_DOWN) {
mTouchMode = TOUCH_MODE_TAP;
@@ -3239,6 +3245,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ @Override
public void onTouchModeChanged(boolean isInTouchMode) {
if (isInTouchMode) {
// Get rid of the selection when we enter touch mode
@@ -3299,95 +3306,153 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- final int action = ev.getAction();
-
- View v;
-
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- switch (mTouchMode) {
- case TOUCH_MODE_OVERFLING: {
- mFlingRunnable.endFling();
- if (mPositionScroller != null) {
- mPositionScroller.stop();
- }
- mTouchMode = TOUCH_MODE_OVERSCROLL;
- mMotionX = (int) ev.getX();
- mMotionY = mLastY = (int) ev.getY();
- mMotionCorrection = 0;
- mActivePointerId = ev.getPointerId(0);
- mDirection = 0;
+ final int actionMasked = ev.getActionMasked();
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN: {
+ onTouchDown(ev);
break;
}
- default: {
- mActivePointerId = ev.getPointerId(0);
- final int x = (int) ev.getX();
- final int y = (int) ev.getY();
- int motionPosition = pointToPosition(x, y);
- if (!mDataChanged) {
- if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
- && (getAdapter().isEnabled(motionPosition))) {
- // User clicked on an actual view (and was not stopping a fling).
- // It might be a click or a scroll. Assume it is a click until
- // proven otherwise
- mTouchMode = TOUCH_MODE_DOWN;
- // FIXME Debounce
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- } else {
- if (mTouchMode == TOUCH_MODE_FLING) {
- // Stopped a fling. It is a scroll.
- createScrollingCache();
- mTouchMode = TOUCH_MODE_SCROLL;
- mMotionCorrection = 0;
- motionPosition = findMotionRow(y);
- mFlingRunnable.flywheelTouch();
- }
- }
- }
+ case MotionEvent.ACTION_MOVE: {
+ onTouchMove(ev);
+ break;
+ }
+
+ case MotionEvent.ACTION_UP: {
+ onTouchUp(ev);
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL: {
+ onTouchCancel();
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ onSecondaryPointerUp(ev);
+ final int x = mMotionX;
+ final int y = mMotionY;
+ final int motionPosition = pointToPosition(x, y);
if (motionPosition >= 0) {
// Remember where the motion event started
- v = getChildAt(motionPosition - mFirstPosition);
- mMotionViewOriginalTop = v.getTop();
+ final View child = getChildAt(motionPosition - mFirstPosition);
+ mMotionViewOriginalTop = child.getTop();
+ mMotionPosition = motionPosition;
}
+ mLastY = y;
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ // New pointers take over dragging duties
+ final int index = ev.getActionIndex();
+ final int id = ev.getPointerId(index);
+ final int x = (int) ev.getX(index);
+ final int y = (int) ev.getY(index);
+ mMotionCorrection = 0;
+ mActivePointerId = id;
mMotionX = x;
mMotionY = y;
- mMotionPosition = motionPosition;
- mLastY = Integer.MIN_VALUE;
+ final int motionPosition = pointToPosition(x, y);
+ if (motionPosition >= 0) {
+ // Remember where the motion event started
+ final View child = getChildAt(motionPosition - mFirstPosition);
+ mMotionViewOriginalTop = child.getTop();
+ mMotionPosition = motionPosition;
+ }
+ mLastY = y;
break;
}
+ }
+
+ return true;
+ }
+
+ private void onTouchDown(MotionEvent ev) {
+ View v;
+ switch (mTouchMode) {
+ case TOUCH_MODE_OVERFLING: {
+ mFlingRunnable.endFling();
+ if (mPositionScroller != null) {
+ mPositionScroller.stop();
}
+ mTouchMode = TOUCH_MODE_OVERSCROLL;
+ mMotionX = (int) ev.getX();
+ mMotionY = mLastY = (int) ev.getY();
+ mMotionCorrection = 0;
+ mActivePointerId = ev.getPointerId(0);
+ mDirection = 0;
+ break;
+ }
- if (performButtonActionOnTouchDown(ev)) {
- if (mTouchMode == TOUCH_MODE_DOWN) {
- removeCallbacks(mPendingCheckForTap);
+ default: {
+ mActivePointerId = ev.getPointerId(0);
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ int motionPosition = pointToPosition(x, y);
+ if (!mDataChanged) {
+ if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
+ && (getAdapter().isEnabled(motionPosition))) {
+ // User clicked on an actual view (and was not stopping a fling).
+ // It might be a click or a scroll. Assume it is a click until
+ // proven otherwise
+ mTouchMode = TOUCH_MODE_DOWN;
+ // FIXME Debounce
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
+ }
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+ } else {
+ if (mTouchMode == TOUCH_MODE_FLING) {
+ // Stopped a fling. It is a scroll.
+ createScrollingCache();
+ mTouchMode = TOUCH_MODE_SCROLL;
+ mMotionCorrection = 0;
+ motionPosition = findMotionRow(y);
+ mFlingRunnable.flywheelTouch();
+ }
}
}
+
+ if (motionPosition >= 0) {
+ // Remember where the motion event started
+ v = getChildAt(motionPosition - mFirstPosition);
+ mMotionViewOriginalTop = v.getTop();
+ }
+ mMotionX = x;
+ mMotionY = y;
+ mMotionPosition = motionPosition;
+ mLastY = Integer.MIN_VALUE;
break;
}
+ }
- case MotionEvent.ACTION_MOVE: {
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == -1) {
- pointerIndex = 0;
- mActivePointerId = ev.getPointerId(pointerIndex);
+ if (performButtonActionOnTouchDown(ev)) {
+ if (mTouchMode == TOUCH_MODE_DOWN) {
+ removeCallbacks(mPendingCheckForTap);
}
- final int y = (int) ev.getY(pointerIndex);
+ }
+ }
- if (mDataChanged) {
- // Re-sync everything if data has been changed
- // since the scroll operation can query the adapter.
- layoutChildren();
- }
+ private void onTouchMove(MotionEvent ev) {
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ pointerIndex = 0;
+ mActivePointerId = ev.getPointerId(pointerIndex);
+ }
- switch (mTouchMode) {
+ if (mDataChanged) {
+ // Re-sync everything if data has been changed
+ // since the scroll operation can query the adapter.
+ layoutChildren();
+ }
+
+ final int y = (int) ev.getY(pointerIndex);
+
+ switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
@@ -3399,262 +3464,219 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded(y);
break;
- }
- break;
}
+ }
- case MotionEvent.ACTION_UP: {
- switch (mTouchMode) {
- case TOUCH_MODE_DOWN:
- case TOUCH_MODE_TAP:
- case TOUCH_MODE_DONE_WAITING:
- final int motionPosition = mMotionPosition;
- final View child = getChildAt(motionPosition - mFirstPosition);
+ private void onTouchUp(MotionEvent ev) {
+ switch (mTouchMode) {
+ case TOUCH_MODE_DOWN:
+ case TOUCH_MODE_TAP:
+ case TOUCH_MODE_DONE_WAITING:
+ final int motionPosition = mMotionPosition;
+ final View child = getChildAt(motionPosition - mFirstPosition);
- final float x = ev.getX();
- final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
+ final float x = ev.getX();
+ final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
- if (child != null && !child.hasFocusable() && inList) {
- if (mTouchMode != TOUCH_MODE_DOWN) {
- child.setPressed(false);
- }
+ if (child != null && !child.hasFocusable() && inList) {
+ if (mTouchMode != TOUCH_MODE_DOWN) {
+ child.setPressed(false);
+ }
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
+ if (mPerformClick == null) {
+ mPerformClick = new PerformClick();
+ }
- final AbsListView.PerformClick performClick = mPerformClick;
- performClick.mClickMotionPosition = motionPosition;
- performClick.rememberWindowAttachCount();
+ final AbsListView.PerformClick performClick = mPerformClick;
+ performClick.mClickMotionPosition = motionPosition;
+ performClick.rememberWindowAttachCount();
- mResurrectToPosition = motionPosition;
+ mResurrectToPosition = motionPosition;
- if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
- final Handler handler = getHandler();
- if (handler != null) {
- handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
- mPendingCheckForTap : mPendingCheckForLongPress);
+ if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
+ final Handler handler = getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
+ mPendingCheckForTap : mPendingCheckForLongPress);
+ }
+ mLayoutMode = LAYOUT_NORMAL;
+ if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
+ mTouchMode = TOUCH_MODE_TAP;
+ setSelectedPositionInt(mMotionPosition);
+ layoutChildren();
+ child.setPressed(true);
+ positionSelector(mMotionPosition, child);
+ setPressed(true);
+ if (mSelector != null) {
+ Drawable d = mSelector.getCurrent();
+ if (d != null && d instanceof TransitionDrawable) {
+ ((TransitionDrawable) d).resetTransition();
+ }
}
- mLayoutMode = LAYOUT_NORMAL;
- if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
- mTouchMode = TOUCH_MODE_TAP;
- setSelectedPositionInt(mMotionPosition);
- layoutChildren();
- child.setPressed(true);
- positionSelector(mMotionPosition, child);
- setPressed(true);
- if (mSelector != null) {
- Drawable d = mSelector.getCurrent();
- if (d != null && d instanceof TransitionDrawable) {
- ((TransitionDrawable) d).resetTransition();
+ if (mTouchModeReset != null) {
+ removeCallbacks(mTouchModeReset);
+ }
+ mTouchModeReset = new Runnable() {
+ @Override
+ public void run() {
+ mTouchModeReset = null;
+ mTouchMode = TOUCH_MODE_REST;
+ child.setPressed(false);
+ setPressed(false);
+ if (!mDataChanged) {
+ performClick.run();
}
}
- if (mTouchModeReset != null) {
- removeCallbacks(mTouchModeReset);
- }
- mTouchModeReset = new Runnable() {
- @Override
- public void run() {
- mTouchModeReset = null;
- mTouchMode = TOUCH_MODE_REST;
- child.setPressed(false);
- setPressed(false);
- if (!mDataChanged) {
- performClick.run();
- }
- }
- };
- postDelayed(mTouchModeReset,
- ViewConfiguration.getPressedStateDuration());
- } else {
- mTouchMode = TOUCH_MODE_REST;
- updateSelectorState();
- }
- return true;
- } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
- performClick.run();
+ };
+ postDelayed(mTouchModeReset,
+ ViewConfiguration.getPressedStateDuration());
+ } else {
+ mTouchMode = TOUCH_MODE_REST;
+ updateSelectorState();
}
+ return;
+ } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
+ performClick.run();
}
- mTouchMode = TOUCH_MODE_REST;
- updateSelectorState();
- break;
- case TOUCH_MODE_SCROLL:
- final int childCount = getChildCount();
- if (childCount > 0) {
- final int firstChildTop = getChildAt(0).getTop();
- final int lastChildBottom = getChildAt(childCount - 1).getBottom();
- final int contentTop = mListPadding.top;
- final int contentBottom = getHeight() - mListPadding.bottom;
- if (mFirstPosition == 0 && firstChildTop >= contentTop &&
- mFirstPosition + childCount < mItemCount &&
- lastChildBottom <= getHeight() - contentBottom) {
+ }
+ mTouchMode = TOUCH_MODE_REST;
+ updateSelectorState();
+ break;
+ case TOUCH_MODE_SCROLL:
+ final int childCount = getChildCount();
+ if (childCount > 0) {
+ final int firstChildTop = getChildAt(0).getTop();
+ final int lastChildBottom = getChildAt(childCount - 1).getBottom();
+ final int contentTop = mListPadding.top;
+ final int contentBottom = getHeight() - mListPadding.bottom;
+ if (mFirstPosition == 0 && firstChildTop >= contentTop &&
+ mFirstPosition + childCount < mItemCount &&
+ lastChildBottom <= getHeight() - contentBottom) {
+ mTouchMode = TOUCH_MODE_REST;
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ } else {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+
+ final int initialVelocity = (int)
+ (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
+ // Fling if we have enough velocity and we aren't at a boundary.
+ // Since we can potentially overfling more than we can overscroll, don't
+ // allow the weird behavior where you can scroll to a boundary then
+ // fling further.
+ if (Math.abs(initialVelocity) > mMinimumVelocity &&
+ !((mFirstPosition == 0 &&
+ firstChildTop == contentTop - mOverscrollDistance) ||
+ (mFirstPosition + childCount == mItemCount &&
+ lastChildBottom == contentBottom + mOverscrollDistance))) {
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ }
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+
+ mFlingRunnable.start(-initialVelocity);
+ } else {
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- } else {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-
- final int initialVelocity = (int)
- (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
- // Fling if we have enough velocity and we aren't at a boundary.
- // Since we can potentially overfling more than we can overscroll, don't
- // allow the weird behavior where you can scroll to a boundary then
- // fling further.
- if (Math.abs(initialVelocity) > mMinimumVelocity &&
- !((mFirstPosition == 0 &&
- firstChildTop == contentTop - mOverscrollDistance) ||
- (mFirstPosition + childCount == mItemCount &&
- lastChildBottom == contentBottom + mOverscrollDistance))) {
- if (mFlingRunnable == null) {
- mFlingRunnable = new FlingRunnable();
- }
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
-
- mFlingRunnable.start(-initialVelocity);
- } else {
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- if (mFlingRunnable != null) {
- mFlingRunnable.endFling();
- }
- if (mPositionScroller != null) {
- mPositionScroller.stop();
- }
+ if (mFlingRunnable != null) {
+ mFlingRunnable.endFling();
+ }
+ if (mPositionScroller != null) {
+ mPositionScroller.stop();
}
}
- } else {
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- }
- break;
-
- case TOUCH_MODE_OVERSCROLL:
- if (mFlingRunnable == null) {
- mFlingRunnable = new FlingRunnable();
- }
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
-
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
- if (Math.abs(initialVelocity) > mMinimumVelocity) {
- mFlingRunnable.startOverfling(-initialVelocity);
- } else {
- mFlingRunnable.startSpringback();
}
-
- break;
+ } else {
+ mTouchMode = TOUCH_MODE_REST;
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
+ break;
- setPressed(false);
-
- if (mEdgeGlowTop != null) {
- mEdgeGlowTop.onRelease();
- mEdgeGlowBottom.onRelease();
+ case TOUCH_MODE_OVERSCROLL:
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
}
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
- // Need to redraw since we probably aren't drawing the selector anymore
- invalidate();
-
- final Handler handler = getHandler();
- if (handler != null) {
- handler.removeCallbacks(mPendingCheckForLongPress);
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+ if (Math.abs(initialVelocity) > mMinimumVelocity) {
+ mFlingRunnable.startOverfling(-initialVelocity);
+ } else {
+ mFlingRunnable.startSpringback();
}
- recycleVelocityTracker();
+ break;
+ }
- mActivePointerId = INVALID_POINTER;
+ setPressed(false);
- if (PROFILE_SCROLLING) {
- if (mScrollProfilingStarted) {
- Debug.stopMethodTracing();
- mScrollProfilingStarted = false;
- }
- }
-
- if (mScrollStrictSpan != null) {
- mScrollStrictSpan.finish();
- mScrollStrictSpan = null;
- }
- break;
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
}
- case MotionEvent.ACTION_CANCEL: {
- switch (mTouchMode) {
- case TOUCH_MODE_OVERSCROLL:
- if (mFlingRunnable == null) {
- mFlingRunnable = new FlingRunnable();
- }
- mFlingRunnable.startSpringback();
- break;
+ // Need to redraw since we probably aren't drawing the selector anymore
+ invalidate();
- case TOUCH_MODE_OVERFLING:
- // Do nothing - let it play out.
- break;
+ final Handler handler = getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(mPendingCheckForLongPress);
+ }
- default:
- mTouchMode = TOUCH_MODE_REST;
- setPressed(false);
- View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- motionView.setPressed(false);
- }
- clearScrollingCache();
+ recycleVelocityTracker();
- final Handler handler = getHandler();
- if (handler != null) {
- handler.removeCallbacks(mPendingCheckForLongPress);
- }
+ mActivePointerId = INVALID_POINTER;
- recycleVelocityTracker();
+ if (PROFILE_SCROLLING) {
+ if (mScrollProfilingStarted) {
+ Debug.stopMethodTracing();
+ mScrollProfilingStarted = false;
}
+ }
- if (mEdgeGlowTop != null) {
- mEdgeGlowTop.onRelease();
- mEdgeGlowBottom.onRelease();
- }
- mActivePointerId = INVALID_POINTER;
- break;
+ if (mScrollStrictSpan != null) {
+ mScrollStrictSpan.finish();
+ mScrollStrictSpan = null;
}
+ }
- case MotionEvent.ACTION_POINTER_UP: {
- onSecondaryPointerUp(ev);
- final int x = mMotionX;
- final int y = mMotionY;
- final int motionPosition = pointToPosition(x, y);
- if (motionPosition >= 0) {
- // Remember where the motion event started
- v = getChildAt(motionPosition - mFirstPosition);
- mMotionViewOriginalTop = v.getTop();
- mMotionPosition = motionPosition;
+ private void onTouchCancel() {
+ switch (mTouchMode) {
+ case TOUCH_MODE_OVERSCROLL:
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
}
- mLastY = y;
+ mFlingRunnable.startSpringback();
break;
- }
- case MotionEvent.ACTION_POINTER_DOWN: {
- // New pointers take over dragging duties
- final int index = ev.getActionIndex();
- final int id = ev.getPointerId(index);
- final int x = (int) ev.getX(index);
- final int y = (int) ev.getY(index);
- mMotionCorrection = 0;
- mActivePointerId = id;
- mMotionX = x;
- mMotionY = y;
- final int motionPosition = pointToPosition(x, y);
- if (motionPosition >= 0) {
- // Remember where the motion event started
- v = getChildAt(motionPosition - mFirstPosition);
- mMotionViewOriginalTop = v.getTop();
- mMotionPosition = motionPosition;
- }
- mLastY = y;
+ case TOUCH_MODE_OVERFLING:
+ // Do nothing - let it play out.
break;
- }
+
+ default:
+ mTouchMode = TOUCH_MODE_REST;
+ setPressed(false);
+ View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+ clearScrollingCache();
+
+ final Handler handler = getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(mPendingCheckForLongPress);
+ }
+
+ recycleVelocityTracker();
}
- return true;
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ }
+ mActivePointerId = INVALID_POINTER;
}
@Override
@@ -3733,7 +3755,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (scrollY != 0) {
// Pin to the top/bottom during overscroll
int restoreCount = canvas.save();
- canvas.translate(0, (float) scrollY);
+ canvas.translate(0, scrollY);
mFastScroller.draw(canvas);
canvas.restoreToCount(restoreCount);
} else {
@@ -3945,6 +3967,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private int mLastFlingY;
private final Runnable mCheckFlywheel = new Runnable() {
+ @Override
public void run() {
final int activeId = mActivePointerId;
final VelocityTracker vt = mVelocityTracker;
@@ -4066,6 +4089,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
}
+ @Override
public void run() {
switch (mTouchMode) {
default:
@@ -4455,6 +4479,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
removeCallbacks(this);
}
+ @Override
public void run() {
final int listHeight = getHeight();
final int firstPos = mFirstPosition;
@@ -4637,9 +4662,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* The amount of friction applied to flings. The default value
* is {@link ViewConfiguration#getScrollFriction}.
- *
- * @return A scalar dimensionless value representing the coefficient of
- * friction.
*/
public void setFriction(float friction) {
if (mFlingRunnable == null) {
@@ -4806,6 +4828,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (!isHardwareAccelerated()) {
if (mClearScrollingCache == null) {
mClearScrollingCache = new Runnable() {
+ @Override
public void run() {
if (mCachingStarted) {
mCachingStarted = mCachingActive = false;
@@ -5083,7 +5106,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
requestLayout();
invalidate();
}
-
+
/**
* If there is a selection returns false.
* Otherwise resurrects the selection and returns true if resurrected.
@@ -5706,6 +5729,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return mFiltered;
}
+ @Override
public void onGlobalLayout() {
if (isShown()) {
// Show the popup if we are filtered
@@ -5725,6 +5749,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* For our text watcher that is associated with the text filter. Does
* nothing.
*/
+ @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@@ -5733,6 +5758,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* the actual filtering as the text changes, and takes care of hiding and
* showing the popup displaying the currently entered filter text.
*/
+ @Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mPopup != null && isTextFilterEnabled()) {
int length = s.length();
@@ -5763,9 +5789,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* For our text watcher that is associated with the text filter. Does
* nothing.
*/
+ @Override
public void afterTextChanged(Editable s) {
}
+ @Override
public void onFilterComplete(int count) {
if (mSelectedPosition < 0 && count > 0) {
mResurrectToPosition = INVALID_POSITION;
@@ -5934,6 +5962,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
* connected yet.
*/
+ @Override
public void deferNotifyDataSetChanged() {
mDeferNotifyDataSetChanged = true;
}
@@ -5941,6 +5970,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Called back when the adapter connects to the RemoteViewsService.
*/
+ @Override
public boolean onRemoteAdapterConnected() {
if (mRemoteAdapter != mAdapter) {
setAdapter(mRemoteAdapter);
@@ -5959,6 +5989,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Called back when the adapter disconnects from the RemoteViewsService.
*/
+ @Override
public void onRemoteAdapterDisconnected() {
// If the remote adapter disconnects, we keep it around
// since the currently displayed items are still cached.
@@ -6041,6 +6072,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return mWrapped != null;
}
+ @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (mWrapped.onCreateActionMode(mode, menu)) {
// Initialize checked graphic state?
@@ -6050,14 +6082,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return false;
}
+ @Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return mWrapped.onPrepareActionMode(mode, menu);
}
+ @Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return mWrapped.onActionItemClicked(mode, item);
}
+ @Override
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
mChoiceActionMode = null;
@@ -6072,6 +6107,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
setLongClickable(true);
}
+ @Override
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked) {
mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f57f333..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;
@@ -181,7 +193,10 @@ public class Editor {
// Set when this TextView gained focus with some text selected. Will start selection mode.
boolean mCreatedWithASelection;
- private EasyEditSpanController mEasyEditSpanController;
+ // The span controller helps monitoring the changes to which the Editor needs to react:
+ // - EasyEditSpans, for which we have some UI to display on attach and on hide
+ // - SelectionSpans, for which we need to call updateSelection if an IME is attached
+ private SpanController mSpanController;
WordIterator mWordIterator;
SpellChecker mSpellChecker;
@@ -466,8 +481,8 @@ public class Editor {
}
private void hideSpanControllers() {
- if (mEasyEditSpanController != null) {
- mEasyEditSpanController.hide();
+ if (mSpanController != null) {
+ mSpanController.hide();
}
}
@@ -1082,9 +1097,12 @@ public class Editor {
mTextView.updateAfterEdit();
reportExtractedText();
} else if (ims.mCursorChanged) {
- // Cheezy way to get us to report the current cursor location.
+ // Cheesy way to get us to report the current cursor location.
mTextView.invalidateCursor();
}
+ // sendUpdateSelection knows to avoid sending if the selection did
+ // not actually change.
+ sendUpdateSelection();
}
static final int EXTRACT_NOTHING = -2;
@@ -1205,6 +1223,27 @@ public class Editor {
return false;
}
+ private void sendUpdateSelection() {
+ if (null != mInputMethodState && mInputMethodState.mBatchEditNesting <= 0) {
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ if (null != imm) {
+ final int selectionStart = mTextView.getSelectionStart();
+ final int selectionEnd = mTextView.getSelectionEnd();
+ int candStart = -1;
+ int candEnd = -1;
+ if (mTextView.getText() instanceof Spannable) {
+ final Spannable sp = (Spannable) mTextView.getText();
+ candStart = EditableInputConnection.getComposingSpanStart(sp);
+ candEnd = EditableInputConnection.getComposingSpanEnd(sp);
+ }
+ // InputMethodManager#updateSelection skips sending the message if
+ // none of the parameters have changed since the last time we called it.
+ imm.updateSelection(mTextView,
+ selectionStart, selectionEnd, candStart, candEnd);
+ }
+ }
+ }
+
void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint,
int cursorOffsetVertical) {
final int selectionStart = mTextView.getSelectionStart();
@@ -1222,17 +1261,6 @@ public class Editor {
// input method.
reported = reportExtractedText();
}
- if (!reported && highlight != null) {
- int candStart = -1;
- int candEnd = -1;
- if (mTextView.getText() instanceof Spannable) {
- Spannable sp = (Spannable) mTextView.getText();
- candStart = EditableInputConnection.getComposingSpanStart(sp);
- candEnd = EditableInputConnection.getComposingSpanEnd(sp);
- }
- imm.updateSelection(mTextView,
- selectionStart, selectionEnd, candStart, candEnd);
- }
}
if (imm.isWatchingCursor(mTextView) && highlight != null) {
@@ -1859,17 +1887,18 @@ public class Editor {
text.setSpan(mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
- if (mEasyEditSpanController == null) {
- mEasyEditSpanController = new EasyEditSpanController();
+ if (mSpanController == null) {
+ mSpanController = new SpanController();
}
- text.setSpan(mEasyEditSpanController, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(mSpanController, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
/**
* Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
* pop-up should be displayed.
+ * Also monitors {@link Selection} to call back to the attached input method.
*/
- class EasyEditSpanController implements SpanWatcher {
+ class SpanController implements SpanWatcher {
private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
@@ -1877,9 +1906,18 @@ public class Editor {
private Runnable mHidePopup;
+ // This function is pure but inner classes can't have static functions
+ private boolean isNonIntermediateSelectionSpan(final Spannable text,
+ final Object span) {
+ return (Selection.SELECTION_START == span || Selection.SELECTION_END == span)
+ && (text.getSpanFlags(span) & Spanned.SPAN_INTERMEDIATE) == 0;
+ }
+
@Override
public void onSpanAdded(Spannable text, Object span, int start, int end) {
- if (span instanceof EasyEditSpan) {
+ if (isNonIntermediateSelectionSpan(text, span)) {
+ sendUpdateSelection();
+ } else if (span instanceof EasyEditSpan) {
if (mPopupWindow == null) {
mPopupWindow = new EasyEditPopupWindow();
mHidePopup = new Runnable() {
@@ -1903,7 +1941,7 @@ public class Editor {
int start = editable.getSpanStart(span);
int end = editable.getSpanEnd(span);
if (start >= 0 && end >= 0) {
- sendNotification(EasyEditSpan.TEXT_DELETED, span);
+ sendEasySpanNotification(EasyEditSpan.TEXT_DELETED, span);
mTextView.deleteText_internal(start, end);
}
editable.removeSpan(span);
@@ -1934,7 +1972,9 @@ public class Editor {
@Override
public void onSpanRemoved(Spannable text, Object span, int start, int end) {
- if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
+ if (isNonIntermediateSelectionSpan(text, span)) {
+ sendUpdateSelection();
+ } else if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
hide();
}
}
@@ -1942,9 +1982,11 @@ public class Editor {
@Override
public void onSpanChanged(Spannable text, Object span, int previousStart, int previousEnd,
int newStart, int newEnd) {
- if (mPopupWindow != null && span instanceof EasyEditSpan) {
+ if (isNonIntermediateSelectionSpan(text, span)) {
+ sendUpdateSelection();
+ } else if (mPopupWindow != null && span instanceof EasyEditSpan) {
EasyEditSpan easyEditSpan = (EasyEditSpan) span;
- sendNotification(EasyEditSpan.TEXT_MODIFIED, easyEditSpan);
+ sendEasySpanNotification(EasyEditSpan.TEXT_MODIFIED, easyEditSpan);
text.removeSpan(easyEditSpan);
}
}
@@ -1956,7 +1998,7 @@ public class Editor {
}
}
- private void sendNotification(int textChangedType, EasyEditSpan span) {
+ private void sendEasySpanNotification(int textChangedType, EasyEditSpan span) {
try {
PendingIntent pendingIntent = span.getPendingIntent();
if (pendingIntent != null) {
@@ -1984,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 {
@@ -3929,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/GridLayout.java b/core/java/android/widget/GridLayout.java
index 2309001..b0ab70d 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -24,7 +24,9 @@ import android.graphics.Insets;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.LogPrinter;
import android.util.Pair;
+import android.util.Printer;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -124,6 +126,17 @@ import static java.lang.Math.min;
* GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
* and <em>bottom</em> edges.
*
+ * <h4>Interpretation of GONE</h4>
+ *
+ * For layout purposes, GridLayout treats views whose visibility status is
+ * {@link View#GONE GONE}, as having zero width and height. This is subtly different from
+ * the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked
+ * view was alone in a column, that column would itself collapse to zero width if and only if
+ * no gravity was defined on the view. If gravity was defined, then the gone-marked
+ * view has no effect on the layout and the container should be laid out as if the view
+ * had never been added to it.
+ * These statements apply equally to rows as well as columns, and to groups of rows or columns.
+ *
* <h5>Limitations</h5>
*
* GridLayout does not provide support for the principle of <em>weight</em>, as defined in
@@ -208,10 +221,15 @@ public class GridLayout extends ViewGroup {
// Misc constants
- static final String TAG = GridLayout.class.getName();
static final int MAX_SIZE = 100000;
static final int DEFAULT_CONTAINER_MARGIN = 0;
static final int UNINITIALIZED_HASH = 0;
+ static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName());
+ static final Printer NO_PRINTER = new Printer() {
+ @Override
+ public void println(String x) {
+ }
+ };
// Defaults
@@ -240,6 +258,7 @@ public class GridLayout extends ViewGroup {
int alignmentMode = DEFAULT_ALIGNMENT_MODE;
int defaultGap;
int lastLayoutParamsHashCode = UNINITIALIZED_HASH;
+ Printer printer = LOG_PRINTER;
// Constructors
@@ -556,6 +575,29 @@ public class GridLayout extends ViewGroup {
requestLayout();
}
+ /**
+ * Return the printer that will log diagnostics from this layout.
+ *
+ * @see #setPrinter(android.util.Printer)
+ *
+ * @return the printer associated with this view
+ */
+ public Printer getPrinter() {
+ return printer;
+ }
+
+ /**
+ * Set the printer that will log diagnostics from this layout.
+ * The default value is created by {@link android.util.LogPrinter}.
+ *
+ * @param printer the printer associated with this layout
+ *
+ * @see #getPrinter()
+ */
+ public void setPrinter(Printer printer) {
+ this.printer = (printer == null) ? NO_PRINTER : printer;
+ }
+
// Static utility methods
static int max2(int[] a, int valueIfEmpty) {
@@ -915,7 +957,7 @@ public class GridLayout extends ViewGroup {
protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
if (oldVisibility == GONE || newVisibility == GONE) {
- invalidateStructure();
+ invalidateStructure();
}
}
@@ -935,8 +977,8 @@ public class GridLayout extends ViewGroup {
validateLayoutParams();
lastLayoutParamsHashCode = computeLayoutParamsHashCode();
} else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
- Log.w(TAG, "The fields of some layout parameters were modified in between layout " +
- "operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
+ printer.println("The fields of some layout parameters were modified in between "
+ + "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
invalidateStructure();
consistencyCheck();
}
@@ -1246,6 +1288,7 @@ public class GridLayout extends ViewGroup {
Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class);
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
+ // we must include views that are GONE here, see introductory javadoc
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds();
@@ -1261,6 +1304,7 @@ public class GridLayout extends ViewGroup {
}
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
+ // we must include views that are GONE here, see introductory javadoc
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
@@ -1527,8 +1571,8 @@ public class GridLayout extends ViewGroup {
removed.add(arc);
}
}
- Log.d(TAG, axisName + " constraints: " + arcsToString(culprits) + " are inconsistent; "
- + "permanently removing: " + arcsToString(removed) + ". ");
+ printer.println(axisName + " constraints: " + arcsToString(culprits) +
+ " are inconsistent; permanently removing: " + arcsToString(removed) + ". ");
}
/*
@@ -2666,6 +2710,9 @@ public class GridLayout extends ViewGroup {
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
+ if (view.getVisibility() == GONE) {
+ return 0;
+ }
int baseline = view.getBaseline();
return baseline == -1 ? UNDEFINED : baseline;
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index f42999d..a82bebd 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -2425,24 +2425,37 @@ public class ListView extends AbsListView {
/**
* Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
- * to move to. This can return a position currently not represented by a view on screen
- * but only in the direction given.
+ * to move to. This return a position in the direction given if the selected item
+ * is fully visible.
*
+ * @param selectedView Current selected view to move from
* @param selectedPos Current selected position to move from
* @param direction Direction to move in
* @return Desired selected position after moving in the given direction
*/
- private final int nextSelectedPositionForDirection(int selectedPos, int direction) {
+ private final int nextSelectedPositionForDirection(
+ View selectedView, int selectedPos, int direction) {
int nextSelected;
+
if (direction == View.FOCUS_DOWN) {
- nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
- selectedPos + 1 :
- mFirstPosition;
+ final int listBottom = getHeight() - mListPadding.bottom;
+ if (selectedView != null && selectedView.getBottom() <= listBottom) {
+ nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
+ selectedPos + 1 :
+ mFirstPosition;
+ } else {
+ return INVALID_POSITION;
+ }
} else {
- final int lastPos = mFirstPosition + getChildCount() - 1;
- nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
- selectedPos - 1 :
- lastPos;
+ final int listTop = mListPadding.top;
+ if (selectedView != null && selectedView.getTop() >= listTop) {
+ final int lastPos = mFirstPosition + getChildCount() - 1;
+ nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
+ selectedPos - 1 :
+ lastPos;
+ } else {
+ return INVALID_POSITION;
+ }
}
if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
@@ -2466,7 +2479,7 @@ public class ListView extends AbsListView {
View selectedView = getSelectedView();
int selectedPos = mSelectedPosition;
- int nextSelectedPosition = nextSelectedPositionForDirection(selectedPos, direction);
+ int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
int amountToScroll = amountToScroll(direction, nextSelectedPosition);
// if we are moving focus, we may OVERRIDE the default behavior
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index aeee111..3ff0cee 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -22,9 +22,11 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
+import android.Manifest;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -34,6 +36,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
@@ -50,9 +53,11 @@ import com.android.internal.widget.LockPatternUtils;
*/
/** @hide */
public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
+ private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
private static final String TAG = "RemoteViewsAdapter";
- // The max number of items in the cache
+ // The max number of items in the cache
private static final int sDefaultCacheSize = 40;
// The delay (in millis) to wait until attempting to unbind from a service after a request.
// This ensures that we don't stay continually bound to the service and that it can be destroyed
@@ -63,7 +68,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
private static final int sDefaultLoadingViewHeight = 50;
// Type defs for controlling different messages across the main and worker message queues
- private static final int sDefaultMessageType = 0;
+ private static final int sDefaultMessageType = 0;
private static final int sUnbindServiceMessageType = 1;
private final Context mContext;
@@ -90,7 +95,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
private Handler mMainQueue;
// We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
- // structures;
+ // structures;
private static final HashMap<RemoteViewsCacheKey,
FixedSizeRemoteViewsCache> sCachedRemoteViewsCaches
= new HashMap<RemoteViewsCacheKey,
@@ -155,13 +160,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
try {
RemoteViewsAdapter adapter;
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if (Process.myUid() == Process.SYSTEM_UID
- && (adapter = mAdapter.get()) != null) {
+ if ((adapter = mAdapter.get()) != null) {
+ checkInteractAcrossUsersPermission(context, adapter.mUserId);
mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
new UserHandle(adapter.mUserId));
} else {
- mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
- Process.myUserHandle());
+ Slog.w(TAG, "bind: adapter was null");
}
mIsConnecting = true;
} catch (Exception e) {
@@ -176,12 +180,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
try {
RemoteViewsAdapter adapter;
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if (Process.myUid() == Process.SYSTEM_UID
- && (adapter = mAdapter.get()) != null) {
+ if ((adapter = mAdapter.get()) != null) {
+ checkInteractAcrossUsersPermission(context, adapter.mUserId);
mgr.unbindRemoteViewsService(appWidgetId, intent,
new UserHandle(adapter.mUserId));
} else {
- mgr.unbindRemoteViewsService(appWidgetId, intent, Process.myUserHandle());
+ Slog.w(TAG, "unbind: adapter was null");
}
mIsConnecting = false;
} catch (Exception e) {
@@ -263,7 +267,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Clear the main/worker queues
final RemoteViewsAdapter adapter = mAdapter.get();
if (adapter == null) return;
-
+
adapter.mMainQueue.post(new Runnable() {
@Override
public void run() {
@@ -828,11 +832,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
mRequestedViews = new RemoteViewsFrameLayoutRefSet();
- if (Process.myUid() == Process.SYSTEM_UID) {
- mUserId = new LockPatternUtils(context).getCurrentUser();
- } else {
- mUserId = UserHandle.myUserId();
- }
+ checkInteractAcrossUsersPermission(context, UserHandle.myUserId());
+ mUserId = context.getUserId();
+
// Strip the previously injected app widget id from service intent
if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
@@ -876,6 +878,15 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
}
+ private static void checkInteractAcrossUsersPermission(Context context, int userId) {
+ if (context.getUserId() != userId
+ && context.checkCallingOrSelfPermission(MULTI_USER_PERM)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must have permission " + MULTI_USER_PERM
+ + " to inflate another user's widget");
+ }
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 93a1179..9886bc3 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -226,6 +226,12 @@ public class ScrollBarDrawable extends Drawable {
}
@Override
+ public int getAlpha() {
+ // All elements should have same alpha, just return one of them
+ return mVerticalThumb.getAlpha();
+ }
+
+ @Override
public void setColorFilter(ColorFilter cf) {
if (mVerticalTrack != null) {
mVerticalTrack.setColorFilter(cf);
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index a564c96..b3b95d9 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -95,6 +95,7 @@ public class TextClock extends TextView {
*
* @see #setFormat12Hour(CharSequence)
* @see #getFormat12Hour()
+ *
* @deprecated Let the system use locale-appropriate defaults instead.
*/
public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm a";
@@ -108,6 +109,7 @@ public class TextClock extends TextView {
*
* @see #setFormat24Hour(CharSequence)
* @see #getFormat24Hour()
+ *
* @deprecated Let the system use locale-appropriate defaults instead.
*/
public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm";
@@ -162,9 +164,7 @@ public class TextClock extends TextView {
};
/**
- * Creates a new clock using the default patterns
- * {@link #DEFAULT_FORMAT_24_HOUR} and {@link #DEFAULT_FORMAT_12_HOUR}
- * respectively for the 24-hour and 12-hour modes.
+ * Creates a new clock using the default patterns for the current locale.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
@@ -258,20 +258,26 @@ public class TextClock extends TextView {
}
/**
- * Specifies the formatting pattern used to display the date and/or time
+ * <p>Specifies the formatting pattern used to display the date and/or time
* in 12-hour mode. The formatting pattern syntax is described in
- * {@link DateFormat}.
+ * {@link DateFormat}.</p>
*
- * If this pattern is set to null, {@link #getFormat24Hour()} will be used
+ * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
* even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
- * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and
- * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead.
+ * are set to null, the default pattern for the current locale will be used
+ * instead.</p>
+ *
+ * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
+ * you supply a format string generated by
+ * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
+ * takes care of generating a format string adapted to the desired locale.</p>
+ *
*
* @param format A date/time formatting pattern as described in {@link DateFormat}
*
* @see #getFormat12Hour()
* @see #is24HourModeEnabled()
- * @see #DEFAULT_FORMAT_12_HOUR
+ * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
* @see DateFormat
*
* @attr ref android.R.styleable#TextClock_format12Hour
@@ -300,20 +306,25 @@ public class TextClock extends TextView {
}
/**
- * Specifies the formatting pattern used to display the date and/or time
+ * <p>Specifies the formatting pattern used to display the date and/or time
* in 24-hour mode. The formatting pattern syntax is described in
- * {@link DateFormat}.
+ * {@link DateFormat}.</p>
+ *
+ * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used
+ * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns
+ * are set to null, the default pattern for the current locale will be used
+ * instead.</p>
*
- * If this pattern is set to null, {@link #getFormat12Hour()} will be used
- * even in 24-hour mode. If both 24-hour and 12-hour formatting patterns
- * are set to null, {@link #DEFAULT_FORMAT_24_HOUR} and
- * {@link #DEFAULT_FORMAT_12_HOUR} will be used instead.
+ * <p><strong>Note:</strong> if styling is not needed, it is highly recommended
+ * you supply a format string generated by
+ * {@link DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method
+ * takes care of generating a format string adapted to the desired locale.</p>
*
* @param format A date/time formatting pattern as described in {@link DateFormat}
*
* @see #getFormat24Hour()
* @see #is24HourModeEnabled()
- * @see #DEFAULT_FORMAT_24_HOUR
+ * @see DateFormat#getBestDateTimePattern(java.util.Locale, String)
* @see DateFormat
*
* @attr ref android.R.styleable#TextClock_format24Hour
@@ -334,8 +345,7 @@ public class TextClock extends TextView {
* returned by {@link #getFormat12Hour()} is used instead.
*
* If either one of the formats is null, the other format is used. If
- * both formats are null, the default values {@link #DEFAULT_FORMAT_12_HOUR}
- * and {@link #DEFAULT_FORMAT_24_HOUR} are used instead.
+ * both formats are null, the default formats for the current locale are used.
*
* @return true if time should be displayed in 24-hour format, false if it
* should be displayed in 12-hour format.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 53cf82d..b1692d7 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;
@@ -284,6 +285,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean mPreDrawRegistered;
+ // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
+ // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
+ // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
+ // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
+ // the user holding the movement key down) then we shouldn't prevent the focus from changing.
+ private boolean mPreventDefaultMovement;
+
private TextUtils.TruncateAt mEllipsize;
static class Drawables {
@@ -1503,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.
*
@@ -4394,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);
}
/**
@@ -5268,7 +5331,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onKeyDown(int keyCode, KeyEvent event) {
int which = doKeyDown(keyCode, event, null);
if (which == 0) {
- // Go through default dispatching.
return super.onKeyDown(keyCode, event);
}
@@ -5366,6 +5428,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return 0;
}
+ // If this is the initial keydown, we don't want to prevent a movement away from this view.
+ // While this shouldn't be necessary because any time we're preventing default movement we
+ // should be restricting the focus to remain within this view, thus we'll also receive
+ // the key up event, occasionally key up events will get dropped and we don't want to
+ // prevent the user from traversing out of this on the next key down.
+ if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
+ mPreventDefaultMovement = false;
+ }
+
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (event.hasNoModifiers()) {
@@ -5474,12 +5545,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (doDown) {
- if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
+ if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
+ if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
+ mPreventDefaultMovement = true;
+ }
return 2;
+ }
}
}
- return 0;
+ return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
}
/**
@@ -5512,6 +5587,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
}
+ if (!KeyEvent.isModifierKey(keyCode)) {
+ mPreventDefaultMovement = false;
+ }
+
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
if (event.hasNoModifiers()) {
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 1d85126..aaa1adaa 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -29,6 +29,7 @@ import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -74,6 +75,9 @@ public class Toast {
*/
public static final int LENGTH_LONG = 1;
+ /** @hide */
+ public static final int LENGTH_INFINITE = 2;
+
final Context mContext;
final TN mTN;
int mDuration;
@@ -288,6 +292,61 @@ public class Toast {
tv.setText(s);
}
+ /** @hide */
+ public static Toast makeBar(Context context, int resId, int duration) {
+ return makeBar(context, context.getResources().getText(resId), duration);
+ }
+
+ /** @hide */
+ public static Toast makeBar(Context context, CharSequence text, int duration) {
+ Toast result = new Toast(context);
+
+ LayoutInflater inflate = (LayoutInflater)
+ context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View v = inflate.inflate(com.android.internal.R.layout.toast_bar, null);
+ ((TextView)v.findViewById(android.R.id.message)).setText(text);
+ v.findViewById(android.R.id.button1).setVisibility(View.GONE);
+
+ result.mNextView = v;
+ result.mDuration = duration;
+ result.mTN.mParams.alpha = 0.9f;
+ result.mTN.mParams.windowAnimations = com.android.internal.R.style.Animation_ToastBar;
+
+ return result;
+ }
+
+ /** @hide */
+ public Toast setAction(int resId, Runnable action) {
+ return setAction(mContext.getResources().getText(resId), action);
+ }
+
+ /** @hide */
+ public Toast setAction(CharSequence actionText, final Runnable action) {
+ if (mNextView != null) {
+ TextView text1 = (TextView)mNextView.findViewById(android.R.id.text1);
+ View button1 = mNextView.findViewById(android.R.id.button1);
+ if (text1 != null && button1 != null) {
+ text1.setText(actionText);
+ button1.setVisibility(View.VISIBLE);
+ button1.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (action != null) {
+ action.run();
+ }
+ }});
+ return setInteractive(true);
+ }
+ }
+ throw new RuntimeException("This Toast was not created with Toast.makeBar()");
+ }
+
+ /** @hide */
+ public Toast setInteractive(boolean interactive) {
+ mTN.setInteractive(interactive);
+ return this;
+ }
+
// =======================================================================================
// All the gunk below is the interaction with the Notification Service, which handles
// the proper ordering of these system-wide.
@@ -340,13 +399,20 @@ public class Toast {
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
+ setInteractive(false);
+ }
+
+ private void setInteractive(boolean interactive) {
+ mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | (interactive
+ ? (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH)
+ : WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
}
/**
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index acbb2b1..e092fff 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -1210,6 +1210,10 @@ public class ActionBarImpl extends ActionBar {
mActionView.setIcon(icon);
}
+ public boolean hasIcon() {
+ return mActionView.hasIcon();
+ }
+
@Override
public void setLogo(int resId) {
mActionView.setLogo(resId);
@@ -1220,6 +1224,10 @@ public class ActionBarImpl extends ActionBar {
mActionView.setLogo(logo);
}
+ public boolean hasLogo() {
+ return mActionView.hasLogo();
+ }
+
public void setDefaultDisplayHomeAsUpEnabled(boolean enable) {
if (!mDisplayHomeAsUpSet) {
setDisplayHomeAsUpEnabled(enable);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index c22cd26..a674776 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import android.os.AsyncTask;
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
@@ -621,9 +622,11 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
view = mInflater.inflate(
com.android.internal.R.layout.resolve_list_item, parent, false);
+ final ViewHolder holder = new ViewHolder(view);
+ view.setTag(holder);
+
// Fix the icon size even if we have different sized resources
- ImageView icon = (ImageView)view.findViewById(R.id.icon);
- ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) icon.getLayoutParams();
+ ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
lp.width = lp.height = mIconSize;
} else {
view = convertView;
@@ -633,20 +636,30 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
}
private final void bindView(View view, DisplayResolveInfo info) {
- TextView text = (TextView)view.findViewById(com.android.internal.R.id.text1);
- TextView text2 = (TextView)view.findViewById(com.android.internal.R.id.text2);
- ImageView icon = (ImageView)view.findViewById(R.id.icon);
- text.setText(info.displayLabel);
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ holder.text.setText(info.displayLabel);
if (mShowExtended) {
- text2.setVisibility(View.VISIBLE);
- text2.setText(info.extendedInfo);
+ holder.text2.setVisibility(View.VISIBLE);
+ holder.text2.setText(info.extendedInfo);
} else {
- text2.setVisibility(View.GONE);
+ holder.text2.setVisibility(View.GONE);
}
if (info.displayIcon == null) {
- info.displayIcon = loadIconForResolveInfo(info.ri);
+ new LoadIconTask().execute(info);
}
- icon.setImageDrawable(info.displayIcon);
+ holder.icon.setImageDrawable(info.displayIcon);
+ }
+ }
+
+ static class ViewHolder {
+ public TextView text;
+ public TextView text2;
+ public ImageView icon;
+
+ public ViewHolder(View view) {
+ text = (TextView) view.findViewById(com.android.internal.R.id.text1);
+ text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
+ icon = (ImageView) view.findViewById(R.id.icon);
}
}
@@ -660,5 +673,21 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
}
}
+
+ class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
+ @Override
+ protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
+ final DisplayResolveInfo info = params[0];
+ if (info.displayIcon == null) {
+ info.displayIcon = loadIconForResolveInfo(info.ri);
+ }
+ return info;
+ }
+
+ @Override
+ protected void onPostExecute(DisplayResolveInfo info) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 424c19b..ab871fb 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
+import com.android.internal.os.BackgroundThread;
import java.util.HashSet;
@@ -37,10 +38,6 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
static final IntentFilter sNonDataFilt = new IntentFilter();
static final IntentFilter sExternalFilt = new IntentFilter();
- static final Object sLock = new Object();
- static HandlerThread sBackgroundThread;
- static Handler sBackgroundHandler;
-
static {
sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -79,15 +76,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
}
mRegisteredContext = context;
if (thread == null) {
- synchronized (sLock) {
- if (sBackgroundThread == null) {
- sBackgroundThread = new HandlerThread("PackageMonitor",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- sBackgroundThread.start();
- sBackgroundHandler = new Handler(sBackgroundThread.getLooper());
- }
- mRegisteredHandler = sBackgroundHandler;
- }
+ mRegisteredHandler = BackgroundThread.getHandler();
} else {
mRegisteredHandler = new Handler(thread);
}
diff --git a/core/java/com/android/internal/os/BackgroundThread.java b/core/java/com/android/internal/os/BackgroundThread.java
new file mode 100644
index 0000000..d6f7b20
--- /dev/null
+++ b/core/java/com/android/internal/os/BackgroundThread.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Shared singleton background thread for each process.
+ */
+public final class BackgroundThread extends HandlerThread {
+ private static BackgroundThread sInstance;
+ private static Handler sHandler;
+
+ private BackgroundThread() {
+ super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new BackgroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ }
+ }
+
+ public static BackgroundThread get() {
+ synchronized (BackgroundThread.class) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ public static Handler getHandler() {
+ synchronized (BackgroundThread.class) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a2e8d49..38391df 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -83,7 +83,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 64 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 65 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -659,7 +659,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void logState(Printer pw, String prefix) {
- pw.println(prefix + " mCount=" + mCount
+ pw.println(prefix + "mCount=" + mCount
+ " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+ " mUnpluggedCount=" + mUnpluggedCount);
pw.println(prefix + "mTotalTime=" + mTotalTime
@@ -1044,7 +1044,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public void logState(Printer pw, String prefix) {
super.logState(pw, prefix);
- pw.println(prefix + "mNesting=" + mNesting + "mUpdateTime=" + mUpdateTime
+ pw.println(prefix + "mNesting=" + mNesting + " mUpdateTime=" + mUpdateTime
+ " mAcquireTime=" + mAcquireTime);
}
@@ -2236,6 +2236,14 @@ public final class BatteryStatsImpl extends BatteryStats {
getUidStatsLocked(uid).noteVideoTurnedOffLocked();
}
+ public void noteActivityResumedLocked(int uid) {
+ getUidStatsLocked(uid).noteActivityResumedLocked();
+ }
+
+ public void noteActivityPausedLocked(int uid) {
+ getUidStatsLocked(uid).noteActivityPausedLocked();
+ }
+
public void noteVibratorOnLocked(int uid, long durationMillis) {
getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
}
@@ -2537,6 +2545,8 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mVideoTurnedOn;
StopwatchTimer mVideoTurnedOnTimer;
+ StopwatchTimer mForegroundActivityTimer;
+
BatchTimer mVibratorOnTimer;
Counter[] mUserActivityCounters;
@@ -2772,6 +2782,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public StopwatchTimer createForegroundActivityTimerLocked() {
+ if (mForegroundActivityTimer == null) {
+ mForegroundActivityTimer = new StopwatchTimer(
+ Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables);
+ }
+ return mForegroundActivityTimer;
+ }
+
+ @Override
+ public void noteActivityResumedLocked() {
+ // We always start, since we want multiple foreground PIDs to nest
+ createForegroundActivityTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ }
+
+ @Override
+ public void noteActivityPausedLocked() {
+ if (mForegroundActivityTimer != null) {
+ mForegroundActivityTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
public BatchTimer createVibratorOnTimerLocked() {
if (mVibratorOnTimer == null) {
mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
@@ -2840,6 +2871,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public Timer getForegroundActivityTimer() {
+ return mForegroundActivityTimer;
+ }
+
+ @Override
public Timer getVibratorOnTimer() {
return mVibratorOnTimer;
}
@@ -2915,6 +2951,9 @@ public final class BatteryStatsImpl extends BatteryStats {
active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
active |= mVideoTurnedOn;
}
+ if (mForegroundActivityTimer != null) {
+ active |= !mForegroundActivityTimer.reset(BatteryStatsImpl.this, false);
+ }
if (mVibratorOnTimer != null) {
if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) {
mVibratorOnTimer.detach();
@@ -3014,6 +3053,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mVideoTurnedOnTimer.detach();
mVideoTurnedOnTimer = null;
}
+ if (mForegroundActivityTimer != null) {
+ mForegroundActivityTimer.detach();
+ mForegroundActivityTimer = null;
+ }
if (mUserActivityCounters != null) {
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
mUserActivityCounters[i].detach();
@@ -3095,6 +3138,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ if (mForegroundActivityTimer != null) {
+ out.writeInt(1);
+ mForegroundActivityTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
if (mVibratorOnTimer != null) {
out.writeInt(1);
mVibratorOnTimer.writeToParcel(out, batteryRealtime);
@@ -3200,6 +3249,12 @@ public final class BatteryStatsImpl extends BatteryStats {
mVideoTurnedOnTimer = null;
}
if (in.readInt() != 0) {
+ mForegroundActivityTimer = new StopwatchTimer(
+ Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables, in);
+ } else {
+ mForegroundActivityTimer = null;
+ }
+ if (in.readInt() != 0) {
mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in);
} else {
@@ -3368,14 +3423,14 @@ public final class BatteryStatsImpl extends BatteryStats {
long mSystemTime;
/**
- * Number of times the process has been started.
+ * Amount of time the process was running in the foreground.
*/
- int mStarts;
+ long mForegroundTime;
/**
- * Amount of time the process was running in the foreground.
+ * Number of times the process has been started.
*/
- long mForegroundTime;
+ int mStarts;
/**
* The amount of user time loaded from a previous save.
@@ -3388,14 +3443,14 @@ public final class BatteryStatsImpl extends BatteryStats {
long mLoadedSystemTime;
/**
- * The number of times the process has started from a previous save.
+ * The amount of foreground time loaded from a previous save.
*/
- int mLoadedStarts;
+ long mLoadedForegroundTime;
/**
- * The amount of foreground time loaded from a previous save.
+ * The number of times the process has started from a previous save.
*/
- long mLoadedForegroundTime;
+ int mLoadedStarts;
/**
* The amount of user time loaded from the previous run.
@@ -3408,14 +3463,14 @@ public final class BatteryStatsImpl extends BatteryStats {
long mLastSystemTime;
/**
- * The number of times the process has started from the previous run.
+ * The amount of foreground time loaded from the previous run
*/
- int mLastStarts;
+ long mLastForegroundTime;
/**
- * The amount of foreground time loaded from the previous run
+ * The number of times the process has started from the previous run.
*/
- long mLastForegroundTime;
+ int mLastStarts;
/**
* The amount of user time when last unplugged.
@@ -3428,14 +3483,14 @@ public final class BatteryStatsImpl extends BatteryStats {
long mUnpluggedSystemTime;
/**
- * The number of times the process has started before unplugged.
+ * The amount of foreground time since unplugged.
*/
- int mUnpluggedStarts;
+ long mUnpluggedForegroundTime;
/**
- * The amount of foreground time since unplugged.
+ * The number of times the process has started before unplugged.
*/
- long mUnpluggedForegroundTime;
+ int mUnpluggedStarts;
SamplingCounter[] mSpeedBins;
@@ -3449,8 +3504,8 @@ public final class BatteryStatsImpl extends BatteryStats {
public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedUserTime = mUserTime;
mUnpluggedSystemTime = mSystemTime;
- mUnpluggedStarts = mStarts;
mUnpluggedForegroundTime = mForegroundTime;
+ mUnpluggedStarts = mStarts;
}
public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
@@ -5357,6 +5412,9 @@ public final class BatteryStatsImpl extends BatteryStats {
u.createVideoTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
}
if (in.readInt() != 0) {
+ u.createForegroundActivityTimerLocked().readSummaryFromParcelLocked(in);
+ }
+ if (in.readInt() != 0) {
u.createVibratorOnTimerLocked().readSummaryFromParcelLocked(in);
}
@@ -5410,6 +5468,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Proc p = u.getProcessStatsLocked(procName);
p.mUserTime = p.mLoadedUserTime = in.readLong();
p.mSystemTime = p.mLoadedSystemTime = in.readLong();
+ p.mForegroundTime = p.mLoadedForegroundTime = in.readLong();
p.mStarts = p.mLoadedStarts = in.readInt();
int NSB = in.readInt();
if (NSB > 100) {
@@ -5559,6 +5618,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ if (u.mForegroundActivityTimer != null) {
+ out.writeInt(1);
+ u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
if (u.mVibratorOnTimer != null) {
out.writeInt(1);
u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
@@ -5628,6 +5693,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Proc ps = ent.getValue();
out.writeLong(ps.mUserTime);
out.writeLong(ps.mSystemTime);
+ out.writeLong(ps.mForegroundTime);
out.writeInt(ps.mStarts);
final int N = ps.mSpeedBins.length;
out.writeInt(N);
@@ -5903,7 +5969,7 @@ public final class BatteryStatsImpl extends BatteryStats {
updateKernelWakelocksLocked();
}
- public void dumpLocked(PrintWriter pw) {
+ public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly) {
if (DEBUG) {
Printer pr = new PrintWriterPrinter(pw);
pr.println("*** Screen timer:");
@@ -5935,7 +6001,7 @@ public final class BatteryStatsImpl extends BatteryStats {
pr.println("*** Mobile ifaces:");
pr.println(mMobileIfaces.toString());
}
- super.dumpLocked(pw);
+ super.dumpLocked(pw, isUnpluggedOnly);
}
private NetworkStats mNetworkSummaryCache;
diff --git a/core/java/com/android/internal/os/ProcessStats.java b/core/java/com/android/internal/os/ProcessStats.java
index b1bb8c1..bd0914d 100644
--- a/core/java/com/android/internal/os/ProcessStats.java
+++ b/core/java/com/android/internal/os/ProcessStats.java
@@ -518,6 +518,10 @@ public class ProcessStats {
return pids;
}
+ /**
+ * Returns the total time (in clock ticks, or 1/100 sec) spent executing in
+ * both user and system code.
+ */
public long getCpuTimeForPid(int pid) {
final String statFile = "/proc/" + pid + "/stat";
final long[] statsData = mSinglePidStatsData;
@@ -531,9 +535,9 @@ public class ProcessStats {
}
/**
- * Returns the times spent at each CPU speed, since the last call to this method. If this
- * is the first time, it will return 1 for each value.
- * @return relative times spent at different speed steps.
+ * Returns the delta time (in clock ticks, or 1/100 sec) spent at each CPU
+ * speed, since the last call to this method. If this is the first call, it
+ * will return 1 for each value.
*/
public long[] getLastCpuSpeedTimes() {
if (mCpuSpeedTimes == null) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index fb22df7..ccc0089 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -22,9 +22,11 @@ import static libcore.io.OsConstants.S_IRWXO;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.LocalServerSocket;
+import android.opengl.EGL14;
import android.os.Debug;
import android.os.Process;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.Trace;
import android.util.EventLog;
import android.util.Log;
@@ -59,9 +61,10 @@ import java.util.ArrayList;
* @hide
*/
public class ZygoteInit {
-
private static final String TAG = "Zygote";
+ private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
+
private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
@@ -227,6 +230,13 @@ public class ZygoteInit {
static void preload() {
preloadClasses();
preloadResources();
+ preloadOpenGL();
+ }
+
+ private static void preloadOpenGL() {
+ if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false)) {
+ EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ }
}
/**
@@ -479,7 +489,6 @@ public class ZygoteInit {
OsConstants.CAP_NET_BIND_SERVICE,
OsConstants.CAP_NET_BROADCAST,
OsConstants.CAP_NET_RAW,
- OsConstants.CAP_SYS_BOOT,
OsConstants.CAP_SYS_MODULE,
OsConstants.CAP_SYS_NICE,
OsConstants.CAP_SYS_RESOURCE,
diff --git a/core/java/com/android/internal/policy/IKeyguardExitCallback.aidl b/core/java/com/android/internal/policy/IKeyguardExitCallback.aidl
new file mode 100644
index 0000000..3702712
--- /dev/null
+++ b/core/java/com/android/internal/policy/IKeyguardExitCallback.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy;
+
+oneway interface IKeyguardExitCallback {
+ void onKeyguardExitResult(boolean success);
+}
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
new file mode 100644
index 0000000..880464d
--- /dev/null
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy;
+
+import com.android.internal.policy.IKeyguardShowCallback;
+import com.android.internal.policy.IKeyguardExitCallback;
+
+import android.os.Bundle;
+
+interface IKeyguardService {
+ boolean isShowing();
+ boolean isSecure();
+ boolean isShowingAndNotHidden();
+ boolean isInputRestricted();
+ boolean isDismissable();
+ oneway void verifyUnlock(IKeyguardExitCallback callback);
+ oneway void keyguardDone(boolean authenticated, boolean wakeup);
+ oneway void setHidden(boolean isHidden);
+ oneway void dismiss();
+ oneway void onWakeKeyWhenKeyguardShowing(int keyCode);
+ oneway void onWakeMotionWhenKeyguardShowing();
+ oneway void onDreamingStarted();
+ oneway void onDreamingStopped();
+ oneway void onScreenTurnedOff(int reason);
+ oneway void onScreenTurnedOn(IKeyguardShowCallback callback);
+ oneway void setKeyguardEnabled(boolean enabled);
+ oneway void onSystemReady();
+ oneway void doKeyguardTimeout(in Bundle options);
+ oneway void setCurrentUser(int userId);
+ oneway void showAssistant();
+}
diff --git a/core/java/com/android/internal/policy/IKeyguardShowCallback.aidl b/core/java/com/android/internal/policy/IKeyguardShowCallback.aidl
new file mode 100644
index 0000000..a2784d9
--- /dev/null
+++ b/core/java/com/android/internal/policy/IKeyguardShowCallback.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy;
+
+oneway interface IKeyguardShowCallback {
+ void onShown(IBinder windowToken);
+}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index d01a817..6fddd09 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -68,6 +68,10 @@ public class IndentingPrintWriter extends PrintWriter {
print(key + "=" + String.valueOf(value) + " ");
}
+ public void printHexPair(String key, int value) {
+ print(key + "=0x" + Integer.toHexString(value) + " ");
+ }
+
@Override
public void write(char[] buf, int offset, int count) {
final int indentLength = mIndentBuilder.length();
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 95130c8..70e2bfc 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -17,12 +17,13 @@
package com.android.internal.view;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -40,6 +41,21 @@ public final class RotationPolicy {
}
/**
+ * Gets whether the device supports rotation. In general such a
+ * device has an accelerometer and has the portrait and landscape
+ * features.
+ *
+ * @param context Context for accessing system resources.
+ * @return Whether the device supports rotation.
+ */
+ public static boolean isRotationSupported(Context context) {
+ PackageManager pm = context.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)
+ && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
+ && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
+ }
+
+ /**
* Returns true if the device supports the rotation-lock toggle feature
* in the system UI or system bar.
*
@@ -48,7 +64,8 @@ public final class RotationPolicy {
* settings.
*/
public static boolean isRotationLockToggleSupported(Context context) {
- return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ return isRotationSupported(context)
+ && context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
/**
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index dda1a10..f2bd522 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -16,24 +16,12 @@
package com.android.internal.widget;
-import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuItem;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuItemImpl;
-import com.android.internal.view.menu.MenuPresenter;
-import com.android.internal.view.menu.MenuView;
-import com.android.internal.view.menu.SubMenuBuilder;
-
import android.animation.LayoutTransition;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
-import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -42,7 +30,6 @@ import android.os.Parcelable;
import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.CollapsibleActionView;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -62,6 +49,15 @@ import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.ActionMenuPresenter;
+import com.android.internal.view.menu.ActionMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
/**
* @hide
@@ -188,34 +184,8 @@ public class ActionBarView extends AbsActionBarView {
ActionBar.NAVIGATION_MODE_STANDARD);
mTitle = a.getText(R.styleable.ActionBar_title);
mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
-
mLogo = a.getDrawable(R.styleable.ActionBar_logo);
- if (mLogo == null) {
- if (context instanceof Activity) {
- try {
- mLogo = pm.getActivityLogo(((Activity) context).getComponentName());
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Activity component name not found!", e);
- }
- }
- if (mLogo == null) {
- mLogo = appInfo.loadLogo(pm);
- }
- }
-
mIcon = a.getDrawable(R.styleable.ActionBar_icon);
- if (mIcon == null) {
- if (context instanceof Activity) {
- try {
- mIcon = pm.getActivityIcon(((Activity) context).getComponentName());
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Activity component name not found!", e);
- }
- }
- if (mIcon == null) {
- mIcon = appInfo.loadIcon(pm);
- }
- }
final LayoutInflater inflater = LayoutInflater.from(context);
@@ -729,7 +699,11 @@ public class ActionBarView extends AbsActionBarView {
}
public void setIcon(int resId) {
- setIcon(mContext.getResources().getDrawable(resId));
+ setIcon(resId != 0 ? mContext.getResources().getDrawable(resId) : null);
+ }
+
+ public boolean hasIcon() {
+ return mIcon != null;
}
public void setLogo(Drawable logo) {
@@ -740,7 +714,11 @@ public class ActionBarView extends AbsActionBarView {
}
public void setLogo(int resId) {
- setLogo(mContext.getResources().getDrawable(resId));
+ setLogo(resId != 0 ? mContext.getResources().getDrawable(resId) : null);
+ }
+
+ public boolean hasLogo() {
+ return mLogo != null;
}
public void setNavigationMode(int mode) {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d3ead26..521ba81 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.Manifest;
import android.app.ActivityManagerNative;
import android.app.admin.DevicePolicyManager;
import android.appwidget.AppWidgetManager;
@@ -149,6 +150,8 @@ public class LockPatternUtils {
private DevicePolicyManager mDevicePolicyManager;
private ILockSettings mLockSettingsService;
+ private final boolean mMultiUserMode;
+
// The current user is set by KeyguardViewMediator and shared by all LockPatternUtils.
private static volatile int sCurrentUserId = UserHandle.USER_NULL;
@@ -170,6 +173,12 @@ public class LockPatternUtils {
public LockPatternUtils(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
+
+ // If this is being called by the system or by an application like keyguard that
+ // has permision INTERACT_ACROSS_USERS, then LockPatternUtils will operate in multi-user
+ // mode where calls are for the current user rather than the user of the calling process.
+ mMultiUserMode = context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL) == PackageManager.PERMISSION_GRANTED;
}
private ILockSettings getLockSettings() {
@@ -264,13 +273,12 @@ public class LockPatternUtils {
}
private int getCurrentOrCallingUserId() {
- int callingUid = Binder.getCallingUid();
- if (callingUid == android.os.Process.SYSTEM_UID) {
+ if (mMultiUserMode) {
// TODO: This is a little inefficient. See if all users of this are able to
// handle USER_CURRENT and pass that instead.
return getCurrentUser();
} else {
- return UserHandle.getUserId(callingUid);
+ return UserHandle.getCallingUserId();
}
}
diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java
deleted file mode 100644
index ca797eb..0000000
--- a/core/java/com/android/internal/widget/TransportControlView.java
+++ /dev/null
@@ -1,515 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import java.lang.ref.WeakReference;
-
-import com.android.internal.widget.LockScreenWidgetCallback;
-import com.android.internal.widget.LockScreenWidgetInterface;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
-import android.media.RemoteControlClient;
-import android.media.IRemoteControlDisplay;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-
-import com.android.internal.R;
-
-public class TransportControlView extends FrameLayout implements OnClickListener,
- LockScreenWidgetInterface {
-
- private static final int MSG_UPDATE_STATE = 100;
- private static final int MSG_SET_METADATA = 101;
- private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
- private static final int MSG_SET_ARTWORK = 103;
- private static final int MSG_SET_GENERATION_ID = 104;
- private static final int MAXDIM = 512;
- private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
- protected static final boolean DEBUG = false;
- protected static final String TAG = "TransportControlView";
-
- private ImageView mAlbumArt;
- private TextView mTrackTitle;
- private ImageView mBtnPrev;
- private ImageView mBtnPlay;
- private ImageView mBtnNext;
- private int mClientGeneration;
- private Metadata mMetadata = new Metadata();
- private boolean mAttached;
- private PendingIntent mClientIntent;
- private int mTransportControlFlags;
- private int mCurrentPlayState;
- private AudioManager mAudioManager;
- private LockScreenWidgetCallback mWidgetCallbacks;
- private IRemoteControlDisplayWeak mIRCD;
-
- /**
- * The metadata which should be populated into the view once we've been attached
- */
- private Bundle mPopulateMetadataWhenAttached = null;
-
- // This handler is required to ensure messages from IRCD are handled in sequence and on
- // the UI thread.
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_STATE:
- if (mClientGeneration == msg.arg1) updatePlayPauseState(msg.arg2);
- break;
-
- case MSG_SET_METADATA:
- if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj);
- break;
-
- case MSG_SET_TRANSPORT_CONTROLS:
- if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2);
- break;
-
- case MSG_SET_ARTWORK:
- if (mClientGeneration == msg.arg1) {
- if (mMetadata.bitmap != null) {
- mMetadata.bitmap.recycle();
- }
- mMetadata.bitmap = (Bitmap) msg.obj;
- mAlbumArt.setImageBitmap(mMetadata.bitmap);
- }
- break;
-
- case MSG_SET_GENERATION_ID:
- if (msg.arg2 != 0) {
- // This means nobody is currently registered. Hide the view.
- if (mWidgetCallbacks != null) {
- mWidgetCallbacks.requestHide(TransportControlView.this);
- }
- }
- if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
- mClientGeneration = msg.arg1;
- mClientIntent = (PendingIntent) msg.obj;
- break;
-
- }
- }
- };
-
- /**
- * This class is required to have weak linkage to the current TransportControlView
- * because the remote process can hold a strong reference to this binder object and
- * we can't predict when it will be GC'd in the remote process. Without this code, it
- * would allow a heavyweight object to be held on this side of the binder when there's
- * no requirement to run a GC on the other side.
- */
- private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub {
- private WeakReference<Handler> mLocalHandler;
-
- IRemoteControlDisplayWeak(Handler handler) {
- mLocalHandler = new WeakReference<Handler>(handler);
- }
-
- public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
- long currentPosMs, float speed) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
- }
- }
-
- public void setMetadata(int generationId, Bundle metadata) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
- }
- }
-
- public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
- .sendToTarget();
- }
- }
-
- public void setArtwork(int generationId, Bitmap bitmap) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
- }
- }
-
- public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
- handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
- }
- }
-
- public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
- boolean clearing) throws RemoteException {
- Handler handler = mLocalHandler.get();
- if (handler != null) {
- handler.obtainMessage(MSG_SET_GENERATION_ID,
- clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
- }
- }
- };
-
- public TransportControlView(Context context, AttributeSet attrs) {
- super(context, attrs);
- if (DEBUG) Log.v(TAG, "Create TCV " + this);
- mAudioManager = new AudioManager(mContext);
- mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
- mIRCD = new IRemoteControlDisplayWeak(mHandler);
- }
-
- private void updateTransportControls(int transportControlFlags) {
- mTransportControlFlags = transportControlFlags;
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- mTrackTitle = (TextView) findViewById(R.id.title);
- mTrackTitle.setSelected(true); // enable marquee
- mAlbumArt = (ImageView) findViewById(R.id.albumart);
- mBtnPrev = (ImageView) findViewById(R.id.btn_prev);
- mBtnPlay = (ImageView) findViewById(R.id.btn_play);
- mBtnNext = (ImageView) findViewById(R.id.btn_next);
- final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext };
- for (View view : buttons) {
- view.setOnClickListener(this);
- }
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (mPopulateMetadataWhenAttached != null) {
- updateMetadata(mPopulateMetadataWhenAttached);
- mPopulateMetadataWhenAttached = null;
- }
- if (!mAttached) {
- if (DEBUG) Log.v(TAG, "Registering TCV " + this);
- mAudioManager.registerRemoteControlDisplay(mIRCD);
- }
- mAttached = true;
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mAttached) {
- if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
- mAudioManager.unregisterRemoteControlDisplay(mIRCD);
- }
- mAttached = false;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int dim = Math.min(MAXDIM, Math.max(getWidth(), getHeight()));
-// Log.v(TAG, "setting max bitmap size: " + dim + "x" + dim);
-// mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
- }
-
- class Metadata {
- private String artist;
- private String trackTitle;
- private String albumTitle;
- private Bitmap bitmap;
-
- public String toString() {
- return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + albumTitle + "]";
- }
- }
-
- private String getMdString(Bundle data, int id) {
- return data.getString(Integer.toString(id));
- }
-
- private void updateMetadata(Bundle data) {
- if (mAttached) {
- mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
- mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
- mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
- populateMetadata();
- } else {
- mPopulateMetadataWhenAttached = data;
- }
- }
-
- /**
- * Populates the given metadata into the view
- */
- private void populateMetadata() {
- StringBuilder sb = new StringBuilder();
- int trackTitleLength = 0;
- if (!TextUtils.isEmpty(mMetadata.trackTitle)) {
- sb.append(mMetadata.trackTitle);
- trackTitleLength = mMetadata.trackTitle.length();
- }
- if (!TextUtils.isEmpty(mMetadata.artist)) {
- if (sb.length() != 0) {
- sb.append(" - ");
- }
- sb.append(mMetadata.artist);
- }
- if (!TextUtils.isEmpty(mMetadata.albumTitle)) {
- if (sb.length() != 0) {
- sb.append(" - ");
- }
- sb.append(mMetadata.albumTitle);
- }
- mTrackTitle.setText(sb.toString(), TextView.BufferType.SPANNABLE);
- Spannable str = (Spannable) mTrackTitle.getText();
- if (trackTitleLength != 0) {
- str.setSpan(new ForegroundColorSpan(0xffffffff), 0, trackTitleLength,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- trackTitleLength++;
- }
- if (sb.length() > trackTitleLength) {
- str.setSpan(new ForegroundColorSpan(0x7fffffff), trackTitleLength, sb.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
- mAlbumArt.setImageBitmap(mMetadata.bitmap);
- final int flags = mTransportControlFlags;
- setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
- setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
- setVisibilityBasedOnFlag(mBtnPlay, flags,
- RemoteControlClient.FLAG_KEY_MEDIA_PLAY
- | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
- | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
- | RemoteControlClient.FLAG_KEY_MEDIA_STOP);
-
- updatePlayPauseState(mCurrentPlayState);
- }
-
- private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
- if ((flags & flag) != 0) {
- view.setVisibility(View.VISIBLE);
- } else {
- view.setVisibility(View.GONE);
- }
- }
-
- private void updatePlayPauseState(int state) {
- if (DEBUG) Log.v(TAG,
- "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state);
- if (state == mCurrentPlayState) {
- return;
- }
- final int imageResId;
- final int imageDescId;
- boolean showIfHidden = false;
- switch (state) {
- case RemoteControlClient.PLAYSTATE_ERROR:
- imageResId = com.android.internal.R.drawable.stat_sys_warning;
- // TODO use more specific image description string for warning, but here the "play"
- // message is still valid because this button triggers a play command.
- imageDescId = com.android.internal.R.string.lockscreen_transport_play_description;
- break;
-
- case RemoteControlClient.PLAYSTATE_PLAYING:
- imageResId = com.android.internal.R.drawable.ic_media_pause;
- imageDescId = com.android.internal.R.string.lockscreen_transport_pause_description;
- showIfHidden = true;
- break;
-
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- imageResId = com.android.internal.R.drawable.ic_media_stop;
- imageDescId = com.android.internal.R.string.lockscreen_transport_stop_description;
- showIfHidden = true;
- break;
-
- case RemoteControlClient.PLAYSTATE_PAUSED:
- default:
- imageResId = com.android.internal.R.drawable.ic_media_play;
- imageDescId = com.android.internal.R.string.lockscreen_transport_play_description;
- showIfHidden = false;
- break;
- }
- mBtnPlay.setImageResource(imageResId);
- mBtnPlay.setContentDescription(getResources().getString(imageDescId));
- if (showIfHidden && mWidgetCallbacks != null && !mWidgetCallbacks.isVisible(this)) {
- mWidgetCallbacks.requestShow(this);
- }
- mCurrentPlayState = state;
- }
-
- static class SavedState extends BaseSavedState {
- boolean wasShowing;
-
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- this.wasShowing = in.readInt() != 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(this.wasShowing ? 1 : 0);
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- if (DEBUG) Log.v(TAG, "onSaveInstanceState()");
- Parcelable superState = super.onSaveInstanceState();
- SavedState ss = new SavedState(superState);
- ss.wasShowing = mWidgetCallbacks != null && mWidgetCallbacks.isVisible(this);
- return ss;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- if (DEBUG) Log.v(TAG, "onRestoreInstanceState()");
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
- if (ss.wasShowing && mWidgetCallbacks != null) {
- mWidgetCallbacks.requestShow(this);
- }
- }
-
- public void onClick(View v) {
- int keyCode = -1;
- if (v == mBtnPrev) {
- keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
- } else if (v == mBtnNext) {
- keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
- } else if (v == mBtnPlay) {
- keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
-
- }
- if (keyCode != -1) {
- sendMediaButtonClick(keyCode);
- if (mWidgetCallbacks != null) {
- mWidgetCallbacks.userActivity(this);
- }
- }
- }
-
- private void sendMediaButtonClick(int keyCode) {
- if (mClientIntent == null) {
- // Shouldn't be possible because this view should be hidden in this case.
- Log.e(TAG, "sendMediaButtonClick(): No client is currently registered");
- return;
- }
- // use the registered PendingIntent that will be processed by the registered
- // media button event receiver, which is the component of mClientIntent
- KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- try {
- mClientIntent.send(getContext(), 0, intent);
- } catch (CanceledException e) {
- Log.e(TAG, "Error sending intent for media button down: "+e);
- e.printStackTrace();
- }
-
- keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
- intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- try {
- mClientIntent.send(getContext(), 0, intent);
- } catch (CanceledException e) {
- Log.e(TAG, "Error sending intent for media button up: "+e);
- e.printStackTrace();
- }
- }
-
- public void setCallback(LockScreenWidgetCallback callback) {
- mWidgetCallbacks = callback;
- }
-
- public boolean providesClock() {
- return false;
- }
-
- private boolean wasPlayingRecently(int state, long stateChangeTimeMs) {
- switch (state) {
- case RemoteControlClient.PLAYSTATE_PLAYING:
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- case RemoteControlClient.PLAYSTATE_REWINDING:
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- // actively playing or about to play
- return true;
- case RemoteControlClient.PLAYSTATE_NONE:
- return false;
- case RemoteControlClient.PLAYSTATE_STOPPED:
- case RemoteControlClient.PLAYSTATE_PAUSED:
- case RemoteControlClient.PLAYSTATE_ERROR:
- // we have stopped playing, check how long ago
- if (DEBUG) {
- if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) {
- Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently");
- } else {
- Log.v(TAG, "wasPlayingRecently: time > TIMEOUT");
- }
- }
- return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS);
- default:
- Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()");
- return false;
- }
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
index 30f5f2f..16bec16 100644
--- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
+++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
@@ -46,36 +46,6 @@ public class TargetDrawable {
private boolean mEnabled = true;
private final int mResourceId;
- /* package */ static class DrawableWithAlpha extends Drawable {
- private float mAlpha = 1.0f;
- private Drawable mRealDrawable;
- public DrawableWithAlpha(Drawable realDrawable) {
- mRealDrawable = realDrawable;
- }
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- }
- public float getAlpha() {
- return mAlpha;
- }
- public void draw(Canvas canvas) {
- mRealDrawable.setAlpha((int) Math.round(mAlpha * 255f));
- mRealDrawable.draw(canvas);
- }
- @Override
- public void setAlpha(int alpha) {
- mRealDrawable.setAlpha(alpha);
- }
- @Override
- public void setColorFilter(ColorFilter cf) {
- mRealDrawable.setColorFilter(cf);
- }
- @Override
- public int getOpacity() {
- return mRealDrawable.getOpacity();
- }
- }
-
public TargetDrawable(Resources res, int resId) {
mResourceId = resId;
setDrawable(res, resId);