summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/animation/AnimatorInflater.java4
-rw-r--r--core/java/android/animation/LayoutTransition.java67
-rw-r--r--core/java/android/app/ActionBar.java9
-rw-r--r--core/java/android/app/ActivityThread.java10
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java8
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java11
-rw-r--r--core/java/android/content/res/AssetManager.java1
-rw-r--r--core/java/android/content/res/ConfigurationBoundResourceCache.java146
-rw-r--r--core/java/android/content/res/DrawableCache.java57
-rw-r--r--core/java/android/content/res/Resources.java311
-rw-r--r--core/java/android/content/res/ThemedResourceCache.java233
-rw-r--r--core/java/android/ddm/DdmHandleViewDebug.java16
-rw-r--r--core/java/android/hardware/camera2/CameraCaptureSession.java7
-rw-r--r--core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java28
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java159
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java75
-rw-r--r--core/java/android/text/method/WordIterator.java39
-rw-r--r--core/java/android/transition/Transition.java4
-rw-r--r--core/java/android/util/FloatMath.java10
-rw-r--r--core/java/android/view/GhostView.java3
-rw-r--r--core/java/android/view/View.java134
-rw-r--r--core/java/android/view/ViewDebug.java24
-rw-r--r--core/java/android/view/ViewGroup.java44
-rw-r--r--core/java/android/view/ViewHierarchyEncoder.java201
-rw-r--r--core/java/android/view/WindowManager.java24
-rw-r--r--core/java/android/webkit/WebView.java16
-rw-r--r--core/java/android/widget/AbsListView.java31
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java3
-rw-r--r--core/java/android/widget/ActionMenuView.java14
-rw-r--r--core/java/android/widget/AdapterView.java14
-rw-r--r--core/java/android/widget/CheckedTextView.java9
-rw-r--r--core/java/android/widget/CompoundButton.java11
-rw-r--r--core/java/android/widget/Editor.java211
-rw-r--r--core/java/android/widget/FrameLayout.java13
-rw-r--r--core/java/android/widget/GridView.java9
-rw-r--r--core/java/android/widget/HorizontalScrollView.java9
-rw-r--r--core/java/android/widget/ImageView.java9
-rw-r--r--core/java/android/widget/LinearLayout.java25
-rw-r--r--core/java/android/widget/ListPopupWindow.java5
-rw-r--r--core/java/android/widget/ListView.java10
-rw-r--r--core/java/android/widget/ProgressBar.java13
-rw-r--r--core/java/android/widget/RelativeLayout.java9
-rw-r--r--core/java/android/widget/ScrollView.java9
-rw-r--r--core/java/android/widget/TableRow.java11
-rw-r--r--core/java/android/widget/TextClock.java16
-rw-r--r--core/java/android/widget/TextView.java21
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java21
-rw-r--r--core/java/com/android/internal/transition/EpicenterClipReveal.java233
-rw-r--r--core/java/com/android/internal/transition/EpicenterTranslate.java191
-rw-r--r--core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java328
-rw-r--r--core/java/com/android/internal/util/AsyncChannel.java8
-rw-r--r--core/java/com/android/internal/util/CallbackRegistry.java395
-rwxr-xr-xcore/jni/android/graphics/Bitmap.cpp53
-rw-r--r--core/jni/android/graphics/Bitmap.h4
-rw-r--r--core/jni/android/graphics/BitmapFactory.cpp4
-rw-r--r--core/jni/android/graphics/Graphics.cpp4
-rw-r--r--core/jni/android/graphics/GraphicsJNI.h2
-rw-r--r--core/jni/android_util_AssetManager.cpp8
-rw-r--r--core/jni/android_view_SurfaceControl.cpp2
-rw-r--r--core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.pngbin108 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.pngbin106 -> 0 bytes
-rw-r--r--core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.pngbin105 -> 0 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.pngbin134 -> 0 bytes
-rw-r--r--core/res/res/drawable/text_cursor_material.xml15
-rw-r--r--core/res/res/layout-land/time_picker_material.xml29
-rw-r--r--core/res/res/layout/date_picker_header_material.xml16
-rw-r--r--core/res/res/layout/time_picker_header_material.xml29
-rw-r--r--core/res/res/transition/popup_window_enter.xml15
-rw-r--r--core/res/res/values/attrs.xml9
-rw-r--r--core/tests/coretests/src/android/util/FloatMathTest.java55
-rw-r--r--core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java305
71 files changed, 2714 insertions, 1105 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 427ecce..435d5ab 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -108,7 +108,7 @@ public class AnimatorInflater {
float pathErrorScale) throws NotFoundException {
final ConfigurationBoundResourceCache<Animator> animatorCache = resources
.getAnimatorCache();
- Animator animator = animatorCache.get(id, theme);
+ Animator animator = animatorCache.getInstance(id, theme);
if (animator != null) {
if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
@@ -157,7 +157,7 @@ public class AnimatorInflater {
final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
.getStateListAnimatorCache();
final Theme theme = context.getTheme();
- StateListAnimator animator = cache.get(id, theme);
+ StateListAnimator animator = cache.getInstance(id, theme);
if (animator != null) {
return animator;
}
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 5790682..cdd72be 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* This class enables automatic animations on layout changes in ViewGroup objects. To enable
@@ -757,7 +758,7 @@ public class LayoutTransition {
// reset the inter-animation delay, in case we use it later
staggerDelay = 0;
- final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
+ final ViewTreeObserver observer = parent.getViewTreeObserver();
if (!observer.isAlive()) {
// If the observer's not in a good state, skip the transition
return;
@@ -790,21 +791,9 @@ public class LayoutTransition {
// This is the cleanup step. When we get this rendering event, we know that all of
// the appropriate animations have been set up and run. Now we can clear out the
// layout listeners.
- observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- public boolean onPreDraw() {
- parent.getViewTreeObserver().removeOnPreDrawListener(this);
- int count = layoutChangeListenerMap.size();
- if (count > 0) {
- Collection<View> views = layoutChangeListenerMap.keySet();
- for (View view : views) {
- View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
- view.removeOnLayoutChangeListener(listener);
- }
- }
- layoutChangeListenerMap.clear();
- return true;
- }
- });
+ CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent);
+ observer.addOnPreDrawListener(callback);
+ parent.addOnAttachStateChangeListener(callback);
}
/**
@@ -1499,4 +1488,50 @@ public class LayoutTransition {
View view, int transitionType);
}
+ /**
+ * Utility class to clean up listeners after animations are setup. Cleanup happens
+ * when either the OnPreDrawListener method is called or when the parent is detached,
+ * whichever comes first.
+ */
+ private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener,
+ View.OnAttachStateChangeListener {
+
+ final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap;
+ final ViewGroup parent;
+
+ CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) {
+ this.layoutChangeListenerMap = listenerMap;
+ this.parent = parent;
+ }
+
+ private void cleanup() {
+ parent.getViewTreeObserver().removeOnPreDrawListener(this);
+ parent.removeOnAttachStateChangeListener(this);
+ int count = layoutChangeListenerMap.size();
+ if (count > 0) {
+ Collection<View> views = layoutChangeListenerMap.keySet();
+ for (View view : views) {
+ View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
+ view.removeOnLayoutChangeListener(listener);
+ }
+ layoutChangeListenerMap.clear();
+ }
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ cleanup();
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ cleanup();
+ return true;
+ }
+ };
+
}
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 94e3b66..4d34349 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -33,6 +33,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.Window;
import android.widget.SpinnerAdapter;
import java.lang.annotation.Retention;
@@ -1373,5 +1374,13 @@ public abstract class ActionBar {
* version of the SDK an app can end up statically linking to the new MarginLayoutParams
* overload, causing a crash when running on older platform versions with no other changes.
*/
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("gravity", gravity);
+ }
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index da6d8c5..f16406a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -38,6 +38,7 @@ import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
@@ -4186,9 +4187,14 @@ public final class ActivityThread {
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
return;
}
- configDiff = mConfiguration.diff(config);
- mConfiguration.updateFrom(config);
+
+ configDiff = mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
+
+ final Theme systemTheme = getSystemContext().getTheme();
+ if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
+ systemTheme.rebase();
+ }
}
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 2939322..968c956 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -476,9 +476,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
tempRect.set(0, 0, width, height);
view.getMatrix().mapRect(tempRect);
- ViewGroup parent = (ViewGroup) view.getParent();
- left = leftInParent - tempRect.left + parent.getScrollX();
- top = topInParent - tempRect.top + parent.getScrollY();
+ left = leftInParent - tempRect.left;
+ top = topInParent - tempRect.top;
right = left + width;
bottom = top + height;
}
@@ -506,7 +505,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
ViewGroup parent = (ViewGroup) view.getParent();
Matrix matrix = new Matrix();
parent.transformMatrixToLocal(matrix);
-
+ matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
mSharedElementParentMatrices.add(matrix);
}
}
@@ -521,6 +520,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
// Find the location in the view's parent
ViewGroup parent = (ViewGroup) viewParent;
parent.transformMatrixToLocal(matrix);
+ matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
}
} else {
// The indices of mSharedElementParentMatrices matches the
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 0a77868..ec6f18d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -28,14 +28,11 @@ import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
+import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.app.ActivityThread;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.os.Binder;
import android.util.Log;
import android.util.Pair;
@@ -1255,7 +1252,7 @@ public final class BluetoothAdapter {
* @return true if chipset supports on-chip filtering
*/
public boolean isOffloadedFilteringSupported() {
- if (getState() != STATE_ON) return false;
+ if (!getLeAccess()) return false;
try {
return mService.isOffloadedFilteringSupported();
} catch (RemoteException e) {
@@ -1270,7 +1267,7 @@ public final class BluetoothAdapter {
* @return true if chipset supports on-chip scan batching
*/
public boolean isOffloadedScanBatchingSupported() {
- if (getState() != STATE_ON) return false;
+ if (!getLeAccess()) return false;
try {
return mService.isOffloadedScanBatchingSupported();
} catch (RemoteException e) {
@@ -1286,7 +1283,7 @@ public final class BluetoothAdapter {
* @hide
*/
public boolean isHardwareTrackingFiltersAvailable() {
- if (getState() != STATE_ON) return false;
+ if (!getLeAccess()) return false;
try {
IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
if (iGatt == null) {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 525059f..8d96f5c 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -785,6 +785,7 @@ public final class AssetManager implements AutoCloseable {
private native final void deleteTheme(long theme);
/*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
/*package*/ native static final void copyTheme(long dest, long source);
+ /*package*/ native static final void clearTheme(long theme);
/*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
TypedValue outValue,
boolean resolve);
diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java
index cde7e84..fecda87 100644
--- a/core/java/android/content/res/ConfigurationBoundResourceCache.java
+++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java
@@ -1,138 +1,58 @@
/*
-* Copyright (C) 2014 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.res;
+ * Copyright (C) 2014 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.
+ */
-import android.util.ArrayMap;
-import android.util.LongSparseArray;
-import java.lang.ref.WeakReference;
+package android.content.res;
/**
* A Cache class which can be used to cache resource objects that are easy to clone but more
* expensive to inflate.
- * @hide
+ *
+ * @hide For internal use only.
*/
-public class ConfigurationBoundResourceCache<T> {
-
- private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>> mCache =
- new ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>>();
-
- final Resources mResources;
+public class ConfigurationBoundResourceCache<T> extends ThemedResourceCache<ConstantState<T>> {
+ private final Resources mResources;
/**
- * Creates a Resource cache for the given Resources instance.
+ * Creates a cache for the given Resources instance.
*
- * @param resources The Resource which can be used when creating new instances.
+ * @param resources the resources to use when creating new instances
*/
public ConfigurationBoundResourceCache(Resources resources) {
mResources = resources;
}
/**
- * Adds a new item to the cache.
- *
- * @param key A custom key that uniquely identifies the resource.
- * @param theme The Theme instance where this resource was loaded.
- * @param constantState The constant state that can create new instances of the resource.
+ * If the resource is cached, creates and returns a new instance of it.
*
+ * @param key a key that uniquely identifies the drawable resource
+ * @param theme the theme where the resource will be used
+ * @return a new instance of the resource, or {@code null} if not in
+ * the cache
*/
- public void put(long key, Resources.Theme theme, ConstantState<T> constantState) {
- if (constantState == null) {
- return;
- }
- final String themeKey = theme == null ? "" : theme.getKey();
- LongSparseArray<WeakReference<ConstantState<T>>> themedCache;
- synchronized (this) {
- themedCache = mCache.get(themeKey);
- if (themedCache == null) {
- themedCache = new LongSparseArray<WeakReference<ConstantState<T>>>(1);
- mCache.put(themeKey, themedCache);
- }
- themedCache.put(key, new WeakReference<ConstantState<T>>(constantState));
- }
- }
-
- /**
- * If the resource is cached, creates a new instance of it and returns.
- *
- * @param key The long key which can be used to uniquely identify the resource.
- * @param theme The The Theme instance where we want to load this resource.
- *
- * @return If this resources was loaded before, returns a new instance of it. Otherwise, returns
- * null.
- */
- public T get(long key, Resources.Theme theme) {
- final String themeKey = theme != null ? theme.getKey() : "";
- final LongSparseArray<WeakReference<ConstantState<T>>> themedCache;
- final WeakReference<ConstantState<T>> wr;
- synchronized (this) {
- themedCache = mCache.get(themeKey);
- if (themedCache == null) {
- return null;
- }
- wr = themedCache.get(key);
- }
- if (wr == null) {
- return null;
- }
- final ConstantState entry = wr.get();
+ public T getInstance(long key, Resources.Theme theme) {
+ final ConstantState<T> entry = get(key, theme);
if (entry != null) {
- return (T) entry.newInstance(mResources, theme);
- } else { // our entry has been purged
- synchronized (this) {
- // there is a potential race condition here where this entry may be put in
- // another thread. But we prefer it to minimize lock duration
- themedCache.delete(key);
- }
+ return entry.newInstance(mResources, theme);
}
- return null;
- }
- /**
- * Users of ConfigurationBoundResourceCache must call this method whenever a configuration
- * change happens. On this callback, the cache invalidates all resources that are not valid
- * anymore.
- *
- * @param configChanges The configuration changes
- */
- public void onConfigurationChange(final int configChanges) {
- synchronized (this) {
- final int size = mCache.size();
- for (int i = size - 1; i >= 0; i--) {
- final LongSparseArray<WeakReference<ConstantState<T>>>
- themeCache = mCache.valueAt(i);
- onConfigurationChangeInt(themeCache, configChanges);
- if (themeCache.size() == 0) {
- mCache.removeAt(i);
- }
- }
- }
+ return null;
}
- private void onConfigurationChangeInt(
- final LongSparseArray<WeakReference<ConstantState<T>>> themeCache,
- final int configChanges) {
- final int size = themeCache.size();
- for (int i = size - 1; i >= 0; i--) {
- final WeakReference<ConstantState<T>> wr = themeCache.valueAt(i);
- final ConstantState<T> constantState = wr.get();
- if (constantState == null || Configuration.needNewResources(
- configChanges, constantState.getChangingConfigurations())) {
- themeCache.removeAt(i);
- }
- }
+ @Override
+ public boolean shouldInvalidateEntry(ConstantState<T> entry, int configChanges) {
+ return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
}
-
}
diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java
new file mode 100644
index 0000000..fc70bc6
--- /dev/null
+++ b/core/java/android/content/res/DrawableCache.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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.res;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Class which can be used to cache Drawable resources against a theme.
+ */
+class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
+ private final Resources mResources;
+
+ /**
+ * Creates a cache for the given Resources instance.
+ *
+ * @param resources the resources to use when creating new instances
+ */
+ public DrawableCache(Resources resources) {
+ mResources = resources;
+ }
+
+ /**
+ * If the resource is cached, creates and returns a new instance of it.
+ *
+ * @param key a key that uniquely identifies the drawable resource
+ * @param theme the theme where the resource will be used
+ * @return a new instance of the resource, or {@code null} if not in
+ * the cache
+ */
+ public Drawable getInstance(long key, Resources.Theme theme) {
+ final Drawable.ConstantState entry = get(key, theme);
+ if (entry != null) {
+ return entry.newDrawable(mResources, theme);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) {
+ return false;
+ }
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ae41b69..e65b4ca 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -20,6 +20,7 @@ import android.annotation.AttrRes;
import android.annotation.ColorInt;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
+import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -60,6 +61,7 @@ import android.util.Pools.SynchronizedPool;
import android.util.Slog;
import android.util.TypedValue;
import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
import java.io.IOException;
import java.io.InputStream;
@@ -132,10 +134,8 @@ public class Resources {
// These are protected by mAccessLock.
private final Object mAccessLock = new Object();
private final Configuration mTmpConfig = new Configuration();
- private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache =
- new ArrayMap<>();
- private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache =
- new ArrayMap<>();
+ private final DrawableCache mDrawableCache = new DrawableCache(this);
+ private final DrawableCache mColorDrawableCache = new DrawableCache(this);
private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache =
new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
@@ -1441,7 +1441,7 @@ public class Resources {
AssetManager.applyThemeStyle(mTheme, resId, force);
mThemeResId = resId;
- mKey += Integer.toHexString(resId) + (force ? "! " : " ");
+ mKey.append(resId, force);
}
/**
@@ -1457,7 +1457,7 @@ public class Resources {
AssetManager.copyTheme(mTheme, other.mTheme);
mThemeResId = other.mThemeResId;
- mKey = other.mKey;
+ mKey.setTo(other.getKey());
}
/**
@@ -1765,6 +1765,9 @@ public class Resources {
mTheme = mAssets.createTheme();
}
+ /** Unique key for the series of styles applied to this theme. */
+ private final ThemeKey mKey = new ThemeKey();
+
@SuppressWarnings("hiding")
private final AssetManager mAssets;
private final long mTheme;
@@ -1772,9 +1775,6 @@ public class Resources {
/** Resource identifier for the theme. */
private int mThemeResId = 0;
- /** Unique key for the series of styles applied to this theme. */
- private String mKey = "";
-
// Needed by layoutlib.
/*package*/ long getNativeTheme() {
return mTheme;
@@ -1784,7 +1784,7 @@ public class Resources {
return mThemeResId;
}
- /*package*/ String getKey() {
+ /*package*/ ThemeKey getKey() {
return mKey;
}
@@ -1793,29 +1793,134 @@ public class Resources {
}
/**
- * Parses {@link #mKey} and returns a String array that holds pairs of adjacent Theme data:
- * resource name followed by whether or not it was forced, as specified by
- * {@link #applyStyle(int, boolean)}.
+ * Parses {@link #mKey} and returns a String array that holds pairs of
+ * adjacent Theme data: resource name followed by whether or not it was
+ * forced, as specified by {@link #applyStyle(int, boolean)}.
*
* @hide
*/
@ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true)
public String[] getTheme() {
- String[] themeData = mKey.split(" ");
- String[] themes = new String[themeData.length * 2];
- String theme;
- boolean forced;
-
- for (int i = 0, j = themeData.length - 1; i < themes.length; i += 2, --j) {
- theme = themeData[j];
- forced = theme.endsWith("!");
- themes[i] = forced ?
- getResourceNameFromHexString(theme.substring(0, theme.length() - 1)) :
- getResourceNameFromHexString(theme);
+ final int N = mKey.mCount;
+ final String[] themes = new String[N * 2];
+ for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
+ final int resId = mKey.mResId[i];
+ final boolean forced = mKey.mForce[i];
+ try {
+ themes[i] = getResourceName(resId);
+ } catch (NotFoundException e) {
+ themes[i] = Integer.toHexString(i);
+ }
themes[i + 1] = forced ? "forced" : "not forced";
}
return themes;
}
+
+ /** @hide */
+ public void encode(@NonNull ViewHierarchyEncoder encoder) {
+ encoder.beginObject(this);
+ // TODO: revert after getTheme() is fixed
+ String[] properties = new String[0]; // getTheme();
+ for (int i = 0; i < properties.length; i += 2) {
+ encoder.addProperty(properties[i], properties[i+1]);
+ }
+ encoder.endObject();
+ }
+
+ /**
+ * Rebases the theme against the parent Resource object's current
+ * configuration by re-applying the styles passed to
+ * {@link #applyStyle(int, boolean)}.
+ *
+ * @hide
+ */
+ public void rebase() {
+ AssetManager.clearTheme(mTheme);
+
+ // Reapply the same styles in the same order.
+ for (int i = 0; i < mKey.mCount; i++) {
+ final int resId = mKey.mResId[i];
+ final boolean force = mKey.mForce[i];
+ AssetManager.applyThemeStyle(mTheme, resId, force);
+ }
+ }
+ }
+
+ static class ThemeKey implements Cloneable {
+ int[] mResId;
+ boolean[] mForce;
+ int mCount;
+
+ private int mHashCode = 0;
+
+ public void append(int resId, boolean force) {
+ if (mResId == null) {
+ mResId = new int[4];
+ }
+
+ if (mForce == null) {
+ mForce = new boolean[4];
+ }
+
+ mResId = GrowingArrayUtils.append(mResId, mCount, resId);
+ mForce = GrowingArrayUtils.append(mForce, mCount, force);
+ mCount++;
+
+ mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0);
+ }
+
+ /**
+ * Sets up this key as a deep copy of another key.
+ *
+ * @param other the key to deep copy into this key
+ */
+ public void setTo(ThemeKey other) {
+ mResId = other.mResId == null ? null : other.mResId.clone();
+ mForce = other.mForce == null ? null : other.mForce.clone();
+ mCount = other.mCount;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ final ThemeKey t = (ThemeKey) o;
+ if (mCount != t.mCount) {
+ return false;
+ }
+
+ final int N = mCount;
+ for (int i = 0; i < N; i++) {
+ if (mResId[i] != t.mResId[i] || mForce[i] != t.mForce[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return a shallow copy of this key
+ */
+ @Override
+ public ThemeKey clone() {
+ final ThemeKey other = new ThemeKey();
+ other.mResId = mResId;
+ other.mForce = mForce;
+ other.mCount = mCount;
+ return other;
+ }
}
/**
@@ -1944,8 +2049,8 @@ public class Resources {
+ " final compat is " + mCompatibilityInfo);
}
- clearDrawableCachesLocked(mDrawableCache, configChanges);
- clearDrawableCachesLocked(mColorDrawableCache, configChanges);
+ mDrawableCache.onConfigurationChange(configChanges);
+ mColorDrawableCache.onConfigurationChange(configChanges);
mColorStateListCache.onConfigurationChange(configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
@@ -1983,48 +2088,6 @@ public class Resources {
return configChanges;
}
- private void clearDrawableCachesLocked(
- ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
- int configChanges) {
- final int N = caches.size();
- for (int i = 0; i < N; i++) {
- clearDrawableCacheLocked(caches.valueAt(i), configChanges);
- }
- }
-
- private void clearDrawableCacheLocked(
- LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) {
- if (DEBUG_CONFIG) {
- Log.d(TAG, "Cleaning up drawables config changes: 0x"
- + Integer.toHexString(configChanges));
- }
- final int N = cache.size();
- for (int i = 0; i < N; i++) {
- final WeakReference<ConstantState> ref = cache.valueAt(i);
- if (ref != null) {
- final ConstantState cs = ref.get();
- if (cs != null) {
- if (Configuration.needNewResources(
- configChanges, cs.getChangingConfigurations())) {
- if (DEBUG_CONFIG) {
- Log.d(TAG, "FLUSHING #0x"
- + Long.toHexString(cache.keyAt(i))
- + " / " + cs + " with changes: 0x"
- + Integer.toHexString(cs.getChangingConfigurations()));
- }
- cache.setValueAt(i, null);
- } else if (DEBUG_CONFIG) {
- Log.d(TAG, "(Keeping #0x"
- + Long.toHexString(cache.keyAt(i))
- + " / " + cs + " with changes: 0x"
- + Integer.toHexString(cs.getChangingConfigurations())
- + ")");
- }
- }
- }
- }
- }
-
/**
* {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
* language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
@@ -2436,7 +2499,7 @@ public class Resources {
}
final boolean isColorDrawable;
- final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches;
+ final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
@@ -2452,7 +2515,7 @@ public class Resources {
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme.
if (!mPreloading) {
- final Drawable cachedDrawable = getCachedDrawable(caches, key, theme);
+ final Drawable cachedDrawable = caches.getInstance(key, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
@@ -2479,13 +2542,8 @@ public class Resources {
// Determine if the drawable has unresolved theme attributes. If it
// does, we'll need to apply a theme and store it in a theme-specific
// cache.
- final String cacheKey;
- if (!dr.canApplyTheme()) {
- cacheKey = CACHE_NOT_THEMED;
- } else if (theme == null) {
- cacheKey = CACHE_NULL_THEME;
- } else {
- cacheKey = theme.getKey();
+ final boolean canApplyTheme = dr.canApplyTheme();
+ if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
@@ -2495,15 +2553,14 @@ public class Resources {
// cache: preload, not themed, null theme, or theme-specific.
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
- cacheDrawable(value, isColorDrawable, caches, cacheKey, key, dr);
+ cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
}
- private void cacheDrawable(TypedValue value, boolean isColorDrawable,
- ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
- String cacheKey, long key, Drawable dr) {
+ private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
+ Theme theme, boolean usesTheme, long key, Drawable dr) {
final ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
@@ -2531,54 +2588,12 @@ public class Resources {
}
} else {
synchronized (mAccessLock) {
- LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(cacheKey);
- if (themedCache == null) {
- // Clean out the caches before we add more. This shouldn't
- // happen very often.
- pruneCaches(caches);
- themedCache = new LongSparseArray<>(1);
- caches.put(cacheKey, themedCache);
- }
- themedCache.put(key, new WeakReference<>(cs));
- }
- }
- }
-
- /**
- * Prunes empty caches from the cache map.
- *
- * @param caches The map of caches to prune.
- */
- private void pruneCaches(ArrayMap<String,
- LongSparseArray<WeakReference<ConstantState>>> caches) {
- final int N = caches.size();
- for (int i = N - 1; i >= 0; i--) {
- final LongSparseArray<WeakReference<ConstantState>> cache = caches.valueAt(i);
- if (pruneCache(cache)) {
- caches.removeAt(i);
+ caches.put(key, theme, cs, usesTheme);
}
}
}
/**
- * Prunes obsolete weak references from a cache, returning {@code true} if
- * the cache is empty and should be removed.
- *
- * @param cache The cache of weak references to prune.
- * @return {@code true} if the cache is empty and should be removed.
- */
- private boolean pruneCache(LongSparseArray<WeakReference<ConstantState>> cache) {
- final int N = cache.size();
- for (int i = N - 1; i >= 0; i--) {
- final WeakReference entry = cache.valueAt(i);
- if (entry == null || entry.get() == null) {
- cache.removeAt(i);
- }
- }
- return cache.size() == 0;
- }
-
- /**
* Loads a drawable from XML or resources stream.
*/
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
@@ -2631,51 +2646,6 @@ public class Resources {
return dr;
}
- private Drawable getCachedDrawable(
- ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
- long key, Theme theme) {
- synchronized (mAccessLock) {
- // First search theme-agnostic cache.
- final Drawable unthemedDrawable = getCachedDrawableLocked(
- caches, key, CACHE_NOT_THEMED);
- if (unthemedDrawable != null) {
- return unthemedDrawable;
- }
-
- // Next search theme-specific cache.
- final String themeKey = theme != null ? theme.getKey() : CACHE_NULL_THEME;
- return getCachedDrawableLocked(caches, key, themeKey);
- }
- }
-
- private Drawable getCachedDrawableLocked(
- ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
- long key, String themeKey) {
- final LongSparseArray<WeakReference<ConstantState>> cache = caches.get(themeKey);
- if (cache != null) {
- final ConstantState entry = getConstantStateLocked(cache, key);
- if (entry != null) {
- return entry.newDrawable(this);
- }
- }
- return null;
- }
-
- private ConstantState getConstantStateLocked(
- LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) {
- final WeakReference<ConstantState> wr = drawableCache.get(key);
- if (wr != null) {
- final ConstantState entry = wr.get();
- if (entry != null) {
- return entry;
- } else {
- // Our entry has been purged.
- drawableCache.delete(key);
- }
- }
- return null;
- }
-
@Nullable
ColorStateList loadColorStateList(TypedValue value, int id, Theme theme)
throws NotFoundException {
@@ -2713,8 +2683,7 @@ public class Resources {
}
final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache;
-
- csl = cache.get(key, theme);
+ csl = cache.getInstance(key, theme);
if (csl != null) {
return csl;
}
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
new file mode 100644
index 0000000..9a2d06147
--- /dev/null
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2015 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.res;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
+import android.content.res.Resources.ThemeKey;
+import android.util.LongSparseArray;
+import android.util.ArrayMap;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Data structure used for caching data against themes.
+ *
+ * @param <T> type of data to cache
+ */
+abstract class ThemedResourceCache<T> {
+ private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries;
+ private LongSparseArray<WeakReference<T>> mUnthemedEntries;
+ private LongSparseArray<WeakReference<T>> mNullThemedEntries;
+
+ /**
+ * Adds a new theme-dependent entry to the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme against which this entry was inflated, or
+ * {@code null} if the entry has no theme applied
+ * @param entry the entry to cache
+ */
+ public void put(long key, @Nullable Theme theme, @NonNull T entry) {
+ put(key, theme, entry, true);
+ }
+
+ /**
+ * Adds a new entry to the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme against which this entry was inflated, or
+ * {@code null} if the entry has no theme applied
+ * @param entry the entry to cache
+ * @param usesTheme {@code true} if the entry is affected theme changes,
+ * {@code false} otherwise
+ */
+ public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) {
+ if (entry == null) {
+ return;
+ }
+
+ synchronized (this) {
+ final LongSparseArray<WeakReference<T>> entries;
+ if (!usesTheme) {
+ entries = getUnthemedLocked(true);
+ } else {
+ entries = getThemedLocked(theme, true);
+ }
+ if (entries != null) {
+ entries.put(key, new WeakReference<>(entry));
+ }
+ }
+ }
+
+ /**
+ * Returns an entry from the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme where the entry will be used
+ * @return a cached entry, or {@code null} if not in the cache
+ */
+ @Nullable
+ public T get(long key, @Nullable Theme theme) {
+ // The themed (includes null-themed) and unthemed caches are mutually
+ // exclusive, so we'll give priority to whichever one we think we'll
+ // hit first. Since most of the framework drawables are themed, that's
+ // probably going to be the themed cache.
+ synchronized (this) {
+ final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false);
+ if (themedEntries != null) {
+ final WeakReference<T> themedEntry = themedEntries.get(key);
+ if (themedEntry != null) {
+ return themedEntry.get();
+ }
+ }
+
+ final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false);
+ if (unthemedEntries != null) {
+ final WeakReference<T> unthemedEntry = unthemedEntries.get(key);
+ if (unthemedEntry != null) {
+ return unthemedEntry.get();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prunes cache entries that have been invalidated by a configuration
+ * change.
+ *
+ * @param configChanges a bitmask of configuration changes
+ */
+ public void onConfigurationChange(int configChanges) {
+ prune(configChanges);
+ }
+
+ /**
+ * Returns whether a cached entry has been invalidated by a configuration
+ * change.
+ *
+ * @param entry a cached entry
+ * @param configChanges a non-zero bitmask of configuration changes
+ * @return {@code true} if the entry is invalid, {@code false} otherwise
+ */
+ protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges);
+
+ /**
+ * Returns the cached data for the specified theme, optionally creating a
+ * new entry if one does not already exist.
+ *
+ * @param t the theme for which to return cached data
+ * @param create {@code true} to create an entry if one does not already
+ * exist, {@code false} otherwise
+ * @return the cached data for the theme, or {@code null} if the cache is
+ * empty and {@code create} was {@code false}
+ */
+ @Nullable
+ private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) {
+ if (t == null) {
+ if (mNullThemedEntries == null && create) {
+ mNullThemedEntries = new LongSparseArray<>(1);
+ }
+ return mNullThemedEntries;
+ }
+
+ if (mThemedEntries == null) {
+ if (create) {
+ mThemedEntries = new ArrayMap<>(1);
+ } else {
+ return null;
+ }
+ }
+
+ final ThemeKey key = t.getKey();
+ LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key);
+ if (cache == null && create) {
+ cache = new LongSparseArray<>(1);
+
+ final ThemeKey keyClone = key.clone();
+ mThemedEntries.put(keyClone, cache);
+ }
+
+ return cache;
+ }
+
+ /**
+ * Returns the theme-agnostic cached data.
+ *
+ * @param create {@code true} to create an entry if one does not already
+ * exist, {@code false} otherwise
+ * @return the theme-agnostic cached data, or {@code null} if the cache is
+ * empty and {@code create} was {@code false}
+ */
+ @Nullable
+ private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) {
+ if (mUnthemedEntries == null && create) {
+ mUnthemedEntries = new LongSparseArray<>(1);
+ }
+ return mUnthemedEntries;
+ }
+
+ /**
+ * Prunes cache entries affected by configuration changes or where weak
+ * references have expired.
+ *
+ * @param configChanges a bitmask of configuration changes, or {@code 0} to
+ * simply prune missing weak references
+ * @return {@code true} if the cache is completely empty after pruning
+ */
+ private boolean prune(int configChanges) {
+ synchronized (this) {
+ if (mThemedEntries != null) {
+ for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
+ if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
+ mThemedEntries.removeAt(i);
+ }
+ }
+ }
+
+ pruneEntriesLocked(mNullThemedEntries, configChanges);
+ pruneEntriesLocked(mUnthemedEntries, configChanges);
+
+ return mThemedEntries == null && mNullThemedEntries == null
+ && mUnthemedEntries == null;
+ }
+ }
+
+ private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
+ int configChanges) {
+ if (entries == null) {
+ return true;
+ }
+
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ final WeakReference<T> ref = entries.valueAt(i);
+ if (ref == null || pruneEntryLocked(ref.get(), configChanges)) {
+ entries.removeAt(i);
+ }
+ }
+
+ return entries.size() == 0;
+ }
+
+ private boolean pruneEntryLocked(@Nullable T entry, int configChanges) {
+ return entry == null || (configChanges != 0
+ && shouldInvalidateEntry(entry, configChanges));
+ }
+}
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
index 3a36b0a..be48633 100644
--- a/core/java/android/ddm/DdmHandleViewDebug.java
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -229,15 +229,25 @@ public class DdmHandleViewDebug extends ChunkHandler {
private Chunk dumpHierarchy(View rootView, ByteBuffer in) {
boolean skipChildren = in.getInt() > 0;
boolean includeProperties = in.getInt() > 0;
+ boolean v2 = in.hasRemaining() && in.getInt() > 0;
- ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ long start = System.currentTimeMillis();
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024);
try {
- ViewDebug.dump(rootView, skipChildren, includeProperties, b);
- } catch (IOException e) {
+ if (v2) {
+ ViewDebug.dumpv2(rootView, b);
+ } else {
+ ViewDebug.dump(rootView, skipChildren, includeProperties, b);
+ }
+ } catch (IOException | InterruptedException e) {
return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
+ e.getMessage());
}
+ long end = System.currentTimeMillis();
+ Log.d(TAG, "Time to obtain view hierarchy (ms): " + (end - start));
+
byte[] data = b.toByteArray();
return new Chunk(CHUNK_VURT, data, 0, data.length);
}
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index aeddf03..ef71c42 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -212,8 +212,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* <p>All capture sessions can be used for capturing images from the camera but only capture
* sessions created by
* {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession}
- * can submit reprocess capture requests. The list of requests must all be capturing images from
- * the camera or all be reprocess capture requests. Submitting a reprocess request to a regular
+ * can submit reprocess capture requests. Submitting a reprocess request to a regular
* capture session will result in an {@link IllegalArgumentException}.</p>
*
* @param requests the list of settings for this burst capture
@@ -236,9 +235,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* @throws IllegalArgumentException If the requests target no Surfaces, or the requests target
* Surfaces not currently configured as outputs; or a reprocess
* capture request is submitted in a non-reprocessible capture
- * session; or the list of requests contains both requests to
- * capture images from the camera and reprocess capture
- * requests; or one of the reprocess capture requests was
+ * session; or one of the reprocess capture requests was
* created with a {@link TotalCaptureResult} from a different
* session; or one of the captures targets a Surface in the
* middle of being {@link #prepare prepared}; or if the handler
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 3c19529..dff6227 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -177,26 +177,20 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
public synchronized int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
Handler handler) throws CameraAccessException {
if (requests == null) {
- throw new IllegalArgumentException("requests must not be null");
+ throw new IllegalArgumentException("Requests must not be null");
} else if (requests.isEmpty()) {
- throw new IllegalArgumentException("requests must have at least one element");
- }
-
- boolean reprocess = requests.get(0).isReprocess();
- if (reprocess && !isReprocessible()) {
- throw new IllegalArgumentException("this capture session cannot handle reprocess " +
- "requests");
- } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) {
- throw new IllegalArgumentException("capture request was created for another session");
+ throw new IllegalArgumentException("Requests must have at least one element");
}
- for (int i = 1; i < requests.size(); i++) {
- if (requests.get(i).isReprocess() != reprocess) {
- throw new IllegalArgumentException("cannot mix regular and reprocess capture " +
- " requests");
- } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) {
- throw new IllegalArgumentException("capture request was created for another " +
- "session");
+ for (CaptureRequest request : requests) {
+ if (request.isReprocess()) {
+ if (!isReprocessible()) {
+ throw new IllegalArgumentException("This capture session cannot handle " +
+ "reprocess requests");
+ } else if (request.getReprocessibleSessionId() != mId) {
+ throw new IllegalArgumentException("Capture request was created for another " +
+ "session");
+ }
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ff4ad79..e84b46a 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -94,11 +94,11 @@ public class CameraDeviceImpl extends CameraDevice {
private final int mTotalPartialCount;
/**
- * A list tracking request and its expected last frame.
- * Updated when calling ICameraDeviceUser methods.
+ * A list tracking request and its expected last regular frame number and last reprocess frame
+ * number. Updated when calling ICameraDeviceUser methods.
*/
- private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>>
- mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>();
+ private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList =
+ new ArrayList<>();
/**
* An object tracking received frame numbers.
@@ -653,8 +653,8 @@ public class CameraDeviceImpl extends CameraDevice {
*
* <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
* sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
- * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
- * is added to the list mFrameNumberRequestPairs.</p>
+ * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last
+ * regular frame number will be added to the list mRequestLastFrameNumbersList.</p>
*
* @param requestId the request ID of the current repeating request.
*
@@ -693,10 +693,6 @@ public class CameraDeviceImpl extends CameraDevice {
"early trigger sequence complete for request %d",
requestId));
}
- if (lastFrameNumber < Integer.MIN_VALUE
- || lastFrameNumber > Integer.MAX_VALUE) {
- throw new AssertionError(lastFrameNumber + " cannot be cast to int");
- }
holder.getCallback().onCaptureSequenceAborted(
CameraDeviceImpl.this,
requestId);
@@ -710,9 +706,11 @@ public class CameraDeviceImpl extends CameraDevice {
requestId));
}
} else {
- mFrameNumberRequestPairs.add(
- new SimpleEntry<Long, Integer>(lastFrameNumber,
- requestId));
+ // This function is only called for regular request so lastFrameNumber is the last
+ // regular frame number.
+ mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId,
+ lastFrameNumber));
+
// It is possible that the last frame has already arrived, so we need to check
// for sequence completion right away
checkAndFireSequenceComplete();
@@ -779,8 +777,8 @@ public class CameraDeviceImpl extends CameraDevice {
}
mRepeatingRequestId = requestId;
} else {
- mFrameNumberRequestPairs.add(
- new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
+ mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestList,
+ requestId, lastFrameNumber));
}
if (mIdle) {
@@ -1146,7 +1144,101 @@ public class CameraDeviceImpl extends CameraDevice {
public int getSessionId() {
return mSessionId;
}
+ }
+
+ /**
+ * This class holds a capture ID and its expected last regular frame number and last reprocess
+ * frame number.
+ */
+ static class RequestLastFrameNumbersHolder {
+ // request ID
+ private final int mRequestId;
+ // The last regular frame number for this request ID. It's
+ // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request.
+ private final long mLastRegularFrameNumber;
+ // The last reprocess frame number for this request ID. It's
+ // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request.
+ private final long mLastReprocessFrameNumber;
+
+ /**
+ * Create a request-last-frame-numbers holder with a list of requests, request ID, and
+ * the last frame number returned by camera service.
+ */
+ public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, int requestId,
+ long lastFrameNumber) {
+ long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
+ long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
+ long frameNumber = lastFrameNumber;
+
+ if (lastFrameNumber < requestList.size() - 1) {
+ throw new IllegalArgumentException("lastFrameNumber: " + lastFrameNumber +
+ " should be at least " + (requestList.size() - 1) + " for the number of " +
+ " requests in the list: " + requestList.size());
+ }
+
+ // find the last regular frame number and the last reprocess frame number
+ for (int i = requestList.size() - 1; i >= 0; i--) {
+ CaptureRequest request = requestList.get(i);
+ if (request.isReprocess() && lastReprocessFrameNumber ==
+ CaptureCallback.NO_FRAMES_CAPTURED) {
+ lastReprocessFrameNumber = frameNumber;
+ } else if (!request.isReprocess() && lastRegularFrameNumber ==
+ CaptureCallback.NO_FRAMES_CAPTURED) {
+ lastRegularFrameNumber = frameNumber;
+ }
+
+ if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED &&
+ lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
+ break;
+ }
+
+ frameNumber--;
+ }
+
+ mLastRegularFrameNumber = lastRegularFrameNumber;
+ mLastReprocessFrameNumber = lastReprocessFrameNumber;
+ mRequestId = requestId;
+ }
+
+ /**
+ * Create a request-last-frame-numbers holder with a request ID and last regular frame
+ * number.
+ */
+ public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) {
+ mLastRegularFrameNumber = lastRegularFrameNumber;
+ mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
+ mRequestId = requestId;
+ }
+
+ /**
+ * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
+ * it contains no regular request.
+ */
+ public long getLastRegularFrameNumber() {
+ return mLastRegularFrameNumber;
+ }
+
+ /**
+ * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
+ * it contains no reprocess request.
+ */
+ public long getLastReprocessFrameNumber() {
+ return mLastReprocessFrameNumber;
+ }
+ /**
+ * Return the last frame number overall.
+ */
+ public long getLastFrameNumber() {
+ return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber);
+ }
+
+ /**
+ * Return the request ID.
+ */
+ public int getRequestId() {
+ return mRequestId;
+ }
}
/**
@@ -1154,8 +1246,8 @@ public class CameraDeviceImpl extends CameraDevice {
*/
public class FrameNumberTracker {
- private long mCompletedFrameNumber = -1;
- private long mCompletedReprocessFrameNumber = -1;
+ private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
+ private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
/** the skipped frame numbers that belong to regular results */
private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>();
/** the skipped frame numbers that belong to reprocess results */
@@ -1360,11 +1452,11 @@ public class CameraDeviceImpl extends CameraDevice {
long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
boolean isReprocess = false;
- Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator();
+ Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator();
while (iter.hasNext()) {
- final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next();
+ final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
boolean sequenceCompleted = false;
- final int requestId = frameNumberRequestPair.getValue();
+ final int requestId = requestLastFrameNumbers.getRequestId();
final CaptureCallbackHolder holder;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) {
@@ -1376,19 +1468,22 @@ public class CameraDeviceImpl extends CameraDevice {
holder = (index >= 0) ?
mCaptureCallbackMap.valueAt(index) : null;
if (holder != null) {
- isReprocess = holder.getRequest().isReprocess();
+ long lastRegularFrameNumber =
+ requestLastFrameNumbers.getLastRegularFrameNumber();
+ long lastReprocessFrameNumber =
+ requestLastFrameNumbers.getLastReprocessFrameNumber();
+
// check if it's okay to remove request from mCaptureCallbackMap
- if ((isReprocess && frameNumberRequestPair.getKey() <=
- completedReprocessFrameNumber) || (!isReprocess &&
- frameNumberRequestPair.getKey() <= completedFrameNumber)) {
+ if (lastRegularFrameNumber <= completedFrameNumber &&
+ lastReprocessFrameNumber <= completedReprocessFrameNumber) {
sequenceCompleted = true;
mCaptureCallbackMap.removeAt(index);
if (DEBUG) {
Log.v(TAG, String.format(
- "remove holder for requestId %d, "
- + "because lastFrame %d is <= %d",
- requestId, frameNumberRequestPair.getKey(),
- completedFrameNumber));
+ "Remove holder for requestId %d, because lastRegularFrame %d " +
+ "is <= %d and lastReprocessFrame %d is <= %d", requestId,
+ lastRegularFrameNumber, completedFrameNumber,
+ lastReprocessFrameNumber, completedReprocessFrameNumber));
}
}
}
@@ -1412,16 +1507,10 @@ public class CameraDeviceImpl extends CameraDevice {
requestId));
}
- long lastFrameNumber = frameNumberRequestPair.getKey();
- if (lastFrameNumber < Integer.MIN_VALUE
- || lastFrameNumber > Integer.MAX_VALUE) {
- throw new AssertionError(lastFrameNumber
- + " cannot be cast to int");
- }
holder.getCallback().onCaptureSequenceCompleted(
CameraDeviceImpl.this,
requestId,
- lastFrameNumber);
+ requestLastFrameNumbers.getLastFrameNumber());
}
}
};
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index cf29310..3eeb04a 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1481,14 +1481,8 @@ public class TextToSpeech {
// interface).
// Sanitize locale using isLanguageAvailable.
- int result = service.isLanguageAvailable( language, country, variant);
- if (result >= LANG_AVAILABLE){
- if (result < LANG_COUNTRY_VAR_AVAILABLE) {
- variant = "";
- if (result < LANG_COUNTRY_AVAILABLE) {
- country = "";
- }
- }
+ int result = service.isLanguageAvailable(language, country, variant);
+ if (result >= LANG_AVAILABLE) {
// Get the default voice for the locale.
String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
if (TextUtils.isEmpty(voiceName)) {
@@ -1502,10 +1496,28 @@ public class TextToSpeech {
return LANG_NOT_SUPPORTED;
}
+ // Set the language/country/variant of the voice, so #getLanguage will return
+ // the currently set voice locale when called.
+ Voice voice = getVoice(service, voiceName);
+ String voiceLanguage = "";
+ try {
+ voiceLanguage = voice.getLocale().getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " +
+ voice.getLocale(), e);
+ }
+
+ String voiceCountry = "";
+ try {
+ voiceCountry = voice.getLocale().getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " +
+ voice.getLocale(), e);
+ }
mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName);
- mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
- mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
- mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
}
return result;
}
@@ -1654,20 +1666,32 @@ public class TextToSpeech {
if (TextUtils.isEmpty(voiceName)) {
return null;
}
- List<Voice> voices = service.getVoices();
- if (voices == null) {
- return null;
- }
- for (Voice voice : voices) {
- if (voice.getName().equals(voiceName)) {
- return voice;
- }
- }
- return null;
+ return getVoice(service, voiceName);
}
}, null, "getVoice");
}
+
+ /**
+ * Returns a Voice instance of the voice with the given voice name.
+ *
+ * @return Voice instance with the given voice name, or {@code null} if not set or on error.
+ *
+ * @see Voice
+ */
+ private Voice getVoice(ITextToSpeechService service, String voiceName) throws RemoteException {
+ List<Voice> voices = service.getVoices();
+ if (voices == null) {
+ return null;
+ }
+ for (Voice voice : voices) {
+ if (voice.getName().equals(voiceName)) {
+ return voice;
+ }
+ }
+ return null;
+ }
+
/**
* Returns a Voice instance that's the default voice for the default Text-to-speech language.
* @return The default voice instance for the default language, or {@code null} if not set or
@@ -1690,14 +1714,7 @@ public class TextToSpeech {
// Sanitize the locale using isLanguageAvailable.
int result = service.isLanguageAvailable(language, country, variant);
- if (result >= LANG_AVAILABLE){
- if (result < LANG_COUNTRY_VAR_AVAILABLE) {
- variant = "";
- if (result < LANG_COUNTRY_AVAILABLE) {
- country = "";
- }
- }
- } else {
+ if (result < LANG_AVAILABLE) {
// The default language is not supported.
return null;
}
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index 11226a9..c4dc5ed 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -95,6 +95,45 @@ public class WordIterator implements Selection.PositionIterator {
} while (true);
}
+ /** {@inheritDoc} */
+ public boolean isBoundary(int offset) {
+ int shiftedOffset = offset - mOffsetShift;
+ checkOffsetIsValid(shiftedOffset);
+ return mIterator.isBoundary(shiftedOffset);
+ }
+
+ /**
+ * Returns the position of next boundary after the given offset. Returns
+ * {@code DONE} if there is no boundary after the given offset.
+ *
+ * @param offset the given start position to search from.
+ * @return the position of the last boundary preceding the given offset.
+ */
+ public int nextBoundary(int offset) {
+ int shiftedOffset = offset - mOffsetShift;
+ shiftedOffset = mIterator.following(shiftedOffset);
+ if (shiftedOffset == BreakIterator.DONE) {
+ return BreakIterator.DONE;
+ }
+ return shiftedOffset + mOffsetShift;
+ }
+
+ /**
+ * Returns the position of boundary preceding the given offset or
+ * {@code DONE} if the given offset specifies the starting position.
+ *
+ * @param offset the given start position to search from.
+ * @return the position of the last boundary preceding the given offset.
+ */
+ public int prevBoundary(int offset) {
+ int shiftedOffset = offset - mOffsetShift;
+ shiftedOffset = mIterator.preceding(shiftedOffset);
+ if (shiftedOffset == BreakIterator.DONE) {
+ return BreakIterator.DONE;
+ }
+ return shiftedOffset + mOffsetShift;
+ }
+
/** If <code>offset</code> is within a word, returns the index of the first character of that
* word, otherwise returns BreakIterator.DONE.
*
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index ebc2aac..1b25505 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -1639,6 +1639,7 @@ public abstract class Transition implements Cloneable {
for (int i = 0; i < count; i++) {
TransitionValues values = lookIn.get(i);
if (values == null) {
+ // Null values are always added to the end of the list, so we know to stop now.
return null;
}
if (values.view == view) {
@@ -1742,6 +1743,9 @@ public abstract class Transition implements Cloneable {
View oldView = oldInfo.view;
TransitionValues startValues = getTransitionValues(oldView, true);
TransitionValues endValues = getMatchedTransitionValues(oldView, true);
+ if (startValues == null && endValues == null) {
+ endValues = mEndValues.viewValues.get(oldView);
+ }
boolean cancel = (startValues != null || endValues != null) &&
oldInfo.transition.areValuesChanged(oldValues, endValues);
if (cancel) {
diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java
index 8f488af..bb7d15f 100644
--- a/core/java/android/util/FloatMath.java
+++ b/core/java/android/util/FloatMath.java
@@ -25,6 +25,8 @@ package android.util;
* {@link java.lang.Math}. {@link java.lang.Math} should be used in
* preference.
*
+ * <p>All methods were removed from the public API in version 23.
+ *
* @deprecated Use {@link java.lang.Math} instead.
*/
@Deprecated
@@ -39,6 +41,7 @@ public class FloatMath {
*
* @param value to be converted
* @return the floor of value
+ * @removed
*/
public static float floor(float value) {
return (float) Math.floor(value);
@@ -50,6 +53,7 @@ public class FloatMath {
*
* @param value to be converted
* @return the ceiling of value
+ * @removed
*/
public static float ceil(float value) {
return (float) Math.ceil(value);
@@ -60,6 +64,7 @@ public class FloatMath {
*
* @param angle to compute the cosine of, in radians
* @return the sine of angle
+ * @removed
*/
public static float sin(float angle) {
return (float) Math.sin(angle);
@@ -70,6 +75,7 @@ public class FloatMath {
*
* @param angle to compute the cosine of, in radians
* @return the cosine of angle
+ * @removed
*/
public static float cos(float angle) {
return (float) Math.cos(angle);
@@ -81,6 +87,7 @@ public class FloatMath {
*
* @param value to compute sqrt of
* @return the square root of value
+ * @removed
*/
public static float sqrt(float value) {
return (float) Math.sqrt(value);
@@ -92,6 +99,7 @@ public class FloatMath {
*
* @param value to compute the exponential of
* @return the exponential of value
+ * @removed
*/
public static float exp(float value) {
return (float) Math.exp(value);
@@ -104,6 +112,7 @@ public class FloatMath {
* @param x the base of the operation.
* @param y the exponent of the operation.
* @return {@code x} to the power of {@code y}.
+ * @removed
*/
public static float pow(float x, float y) {
return (float) Math.pow(x, y);
@@ -116,6 +125,7 @@ public class FloatMath {
* @param x a float number
* @param y a float number
* @return the hypotenuse
+ * @removed
*/
public static float hypot(float x, float y) {
return (float) Math.hypot(x, y);
diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java
index bc38e1a..9f46f45 100644
--- a/core/java/android/view/GhostView.java
+++ b/core/java/android/view/GhostView.java
@@ -40,8 +40,7 @@ public class GhostView extends View {
mView.mGhostView = this;
final ViewGroup parent = (ViewGroup) mView.getParent();
setGhostedVisibility(View.INVISIBLE);
- parent.mRecreateDisplayList = true;
- parent.updateDisplayListIfDirty();
+ parent.invalidate();
}
@Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f62e6a2..a3d0b2a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22353,4 +22353,138 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final String output = bits + " " + name;
found.put(key, output);
}
+
+ /** {@hide} */
+ public void encode(@NonNull ViewHierarchyEncoder stream) {
+ stream.beginObject(this);
+ encodeProperties(stream);
+ stream.endObject();
+ }
+
+ /** {@hide} */
+ @CallSuper
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ Object resolveId = ViewDebug.resolveId(getContext(), mID);
+ if (resolveId instanceof String) {
+ stream.addProperty("id", (String) resolveId);
+ } else {
+ stream.addProperty("id", mID);
+ }
+
+ stream.addProperty("misc:transformation.alpha",
+ mTransformationInfo != null ? mTransformationInfo.mAlpha : 0);
+ stream.addProperty("misc:transitionName", getTransitionName());
+
+ // layout
+ stream.addProperty("layout:left", mLeft);
+ stream.addProperty("layout:right", mRight);
+ stream.addProperty("layout:top", mTop);
+ stream.addProperty("layout:bottom", mBottom);
+ stream.addProperty("layout:width", getWidth());
+ stream.addProperty("layout:height", getHeight());
+ stream.addProperty("layout:layoutDirection", getLayoutDirection());
+ stream.addProperty("layout:layoutRtl", isLayoutRtl());
+ stream.addProperty("layout:hasTransientState", hasTransientState());
+ stream.addProperty("layout:baseline", getBaseline());
+
+ // layout params
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ if (layoutParams != null) {
+ stream.addPropertyKey("layoutParams");
+ layoutParams.encode(stream);
+ }
+
+ // scrolling
+ stream.addProperty("scrolling:scrollX", mScrollX);
+ stream.addProperty("scrolling:scrollY", mScrollY);
+
+ // padding
+ stream.addProperty("padding:paddingLeft", mPaddingLeft);
+ stream.addProperty("padding:paddingRight", mPaddingRight);
+ stream.addProperty("padding:paddingTop", mPaddingTop);
+ stream.addProperty("padding:paddingBottom", mPaddingBottom);
+ stream.addProperty("padding:userPaddingRight", mUserPaddingRight);
+ stream.addProperty("padding:userPaddingLeft", mUserPaddingLeft);
+ stream.addProperty("padding:userPaddingBottom", mUserPaddingBottom);
+ stream.addProperty("padding:userPaddingStart", mUserPaddingStart);
+ stream.addProperty("padding:userPaddingEnd", mUserPaddingEnd);
+
+ // measurement
+ stream.addProperty("measurement:minHeight", mMinHeight);
+ stream.addProperty("measurement:minWidth", mMinWidth);
+ stream.addProperty("measurement:measuredWidth", mMeasuredWidth);
+ stream.addProperty("measurement:measuredHeight", mMeasuredHeight);
+
+ // drawing
+ stream.addProperty("drawing:elevation", getElevation());
+ stream.addProperty("drawing:translationX", getTranslationX());
+ stream.addProperty("drawing:translationY", getTranslationY());
+ stream.addProperty("drawing:translationZ", getTranslationZ());
+ stream.addProperty("drawing:rotation", getRotation());
+ stream.addProperty("drawing:rotationX", getRotationX());
+ stream.addProperty("drawing:rotationY", getRotationY());
+ stream.addProperty("drawing:scaleX", getScaleX());
+ stream.addProperty("drawing:scaleY", getScaleY());
+ stream.addProperty("drawing:pivotX", getPivotX());
+ stream.addProperty("drawing:pivotY", getPivotY());
+ stream.addProperty("drawing:opaque", isOpaque());
+ stream.addProperty("drawing:alpha", getAlpha());
+ stream.addProperty("drawing:transitionAlpha", getTransitionAlpha());
+ stream.addProperty("drawing:shadow", hasShadow());
+ stream.addProperty("drawing:solidColor", getSolidColor());
+ stream.addProperty("drawing:layerType", mLayerType);
+ stream.addProperty("drawing:willNotDraw", willNotDraw());
+ stream.addProperty("drawing:hardwareAccelerated", isHardwareAccelerated());
+ stream.addProperty("drawing:willNotCacheDrawing", willNotCacheDrawing());
+ stream.addProperty("drawing:drawingCacheEnabled", isDrawingCacheEnabled());
+ stream.addProperty("drawing:overlappingRendering", hasOverlappingRendering());
+
+ // focus
+ stream.addProperty("focus:hasFocus", hasFocus());
+ stream.addProperty("focus:isFocused", isFocused());
+ stream.addProperty("focus:isFocusable", isFocusable());
+ stream.addProperty("focus:isFocusableInTouchMode", isFocusableInTouchMode());
+
+ stream.addProperty("misc:clickable", isClickable());
+ stream.addProperty("misc:pressed", isPressed());
+ stream.addProperty("misc:selected", isSelected());
+ stream.addProperty("misc:touchMode", isInTouchMode());
+ stream.addProperty("misc:hovered", isHovered());
+ stream.addProperty("misc:activated", isActivated());
+
+ stream.addProperty("misc:visibility", getVisibility());
+ stream.addProperty("misc:fitsSystemWindows", getFitsSystemWindows());
+ stream.addProperty("misc:filterTouchesWhenObscured", getFilterTouchesWhenObscured());
+
+ stream.addProperty("misc:enabled", isEnabled());
+ stream.addProperty("misc:soundEffectsEnabled", isSoundEffectsEnabled());
+ stream.addProperty("misc:hapticFeedbackEnabled", isHapticFeedbackEnabled());
+
+ // theme attributes
+ Resources.Theme theme = getContext().getTheme();
+ if (theme != null) {
+ stream.addPropertyKey("theme");
+ theme.encode(stream);
+ }
+
+ // view attribute information
+ int n = mAttributes != null ? mAttributes.length : 0;
+ stream.addProperty("meta:__attrCount__", n/2);
+ for (int i = 0; i < n; i += 2) {
+ stream.addProperty("meta:__attr__" + mAttributes[i], mAttributes[i+1]);
+ }
+
+ stream.addProperty("misc:scrollBarStyle", getScrollBarStyle());
+
+ // text
+ stream.addProperty("text:textDirection", getTextDirection());
+ stream.addProperty("text:textAlignment", getTextAlignment());
+
+ // accessibility
+ CharSequence contentDescription = getContentDescription();
+ stream.addProperty("accessibility:contentDescription",
+ contentDescription == null ? "" : contentDescription.toString());
+ stream.addProperty("accessibility:labelFor", getLabelFor());
+ stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility());
+ }
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 27304f5..8bf53a8 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -800,6 +801,7 @@ public class ViewDebug {
/**
* Dumps the view hierarchy starting from the given view.
+ * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below.
* @hide
*/
public static void dump(View root, boolean skipChildren, boolean includeProperties,
@@ -825,6 +827,28 @@ public class ViewDebug {
}
/**
+ * Dumps the view hierarchy starting from the given view.
+ * Rather than using reflection, it uses View's encode method to obtain all the properties.
+ * @hide
+ */
+ public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out)
+ throws InterruptedException {
+ final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out);
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.encode(encoder);
+ latch.countDown();
+ }
+ });
+
+ latch.await(2, TimeUnit.SECONDS);
+ encoder.endStream();
+ }
+
+ /**
* Dumps the theme attributes from the given View.
* @hide
*/
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index babb4e9..d0738b0 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -6861,6 +6861,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
return String.valueOf(size);
}
+
+ /** @hide */
+ void encode(@NonNull ViewHierarchyEncoder encoder) {
+ encoder.beginObject(this);
+ encodeProperties(encoder);
+ encoder.endObject();
+ }
+
+ /** @hide */
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ encoder.addProperty("width", width);
+ encoder.addProperty("height", height);
+ }
}
/**
@@ -7329,6 +7342,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
bottomMargin,
paint);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("leftMargin", leftMargin);
+ encoder.addProperty("topMargin", topMargin);
+ encoder.addProperty("rightMargin", rightMargin);
+ encoder.addProperty("bottomMargin", bottomMargin);
+ encoder.addProperty("startMargin", startMargin);
+ encoder.addProperty("endMargin", endMargin);
+ }
}
/* Describes a touched view and the ids of the pointers that it has captured.
@@ -7665,4 +7690,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
canvas.drawLines(sDebugLines, paint);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("focus:descendantFocusability", getDescendantFocusability());
+ encoder.addProperty("drawing:clipChildren", getClipChildren());
+ encoder.addProperty("drawing:clipToPadding", getClipToPadding());
+ encoder.addProperty("drawing:childrenDrawingOrderEnabled", isChildrenDrawingOrderEnabled());
+ encoder.addProperty("drawing:persistentDrawingCache", getPersistentDrawingCache());
+
+ int n = getChildCount();
+ encoder.addProperty("meta:__childCount__", (short)n);
+ for (int i = 0; i < n; i++) {
+ encoder.addPropertyKey("meta:__child__" + i);
+ getChildAt(i).encode(encoder);
+ }
+ }
}
diff --git a/core/java/android/view/ViewHierarchyEncoder.java b/core/java/android/view/ViewHierarchyEncoder.java
new file mode 100644
index 0000000..8770216
--- /dev/null
+++ b/core/java/android/view/ViewHierarchyEncoder.java
@@ -0,0 +1,201 @@
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
+ * view hierarchies (the view tree, along with the properties for each view) to a stream.
+ *
+ * It is typically used as follows:
+ * <pre>
+ * ViewHierarchyEncoder e = new ViewHierarchyEncoder();
+ *
+ * for (View view : views) {
+ * e.beginObject(view);
+ * e.addProperty("prop1", value);
+ * ...
+ * e.endObject();
+ * }
+ *
+ * // repeat above snippet for each view, finally end with:
+ * e.endStream();
+ * </pre>
+ *
+ * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
+ * corresponding to each view) with the property name as the key and the property value
+ * as the value.
+ *
+ * <p>Since the property names are practically the same across all views, rather than using
+ * the property name directly as the key, we use a short integer id corresponding to each
+ * property name as the key. A final map is added at the end which contains the mapping
+ * from the integer to its property name.
+ *
+ * <p>A value is encoded as a single byte type identifier followed by the encoding of the
+ * value. Only primitive types are supported as values, in addition to the Map type.
+ *
+ * @hide
+ */
+public class ViewHierarchyEncoder {
+ // Prefixes for simple primitives. These match the JNI definitions.
+ private static final byte SIG_BOOLEAN = 'Z';
+ private static final byte SIG_BYTE = 'B';
+ private static final byte SIG_SHORT = 'S';
+ private static final byte SIG_INT = 'I';
+ private static final byte SIG_LONG = 'J';
+ private static final byte SIG_FLOAT = 'F';
+ private static final byte SIG_DOUBLE = 'D';
+
+ // Prefixes for some commonly used objects
+ private static final byte SIG_STRING = 'R';
+
+ private static final byte SIG_MAP = 'M'; // a map with an short key
+ private static final short SIG_END_MAP = 0;
+
+ private final DataOutputStream mStream;
+
+ private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
+ private short mPropertyId = 1;
+ private Charset mCharset = Charset.forName("utf-8");
+
+ public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
+ mStream = new DataOutputStream(stream);
+ }
+
+ public void beginObject(@NonNull Object o) {
+ startPropertyMap();
+ addProperty("meta:__name__", o.getClass().getName());
+ addProperty("meta:__hash__", o.hashCode());
+ }
+
+ public void endObject() {
+ endPropertyMap();
+ }
+
+ public void endStream() {
+ // write out the string table
+ startPropertyMap();
+ addProperty("__name__", "propertyIndex");
+ for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
+ writeShort(entry.getValue());
+ writeString(entry.getKey());
+ }
+ endPropertyMap();
+ }
+
+ public void addProperty(@NonNull String name, boolean v) {
+ writeShort(createPropertyIndex(name));
+ writeBoolean(v);
+ }
+
+ public void addProperty(@NonNull String name, short s) {
+ writeShort(createPropertyIndex(name));
+ writeShort(s);
+ }
+
+ public void addProperty(@NonNull String name, int v) {
+ writeShort(createPropertyIndex(name));
+ writeInt(v);
+ }
+
+ public void addProperty(@NonNull String name, float v) {
+ writeShort(createPropertyIndex(name));
+ writeFloat(v);
+ }
+
+ public void addProperty(@NonNull String name, @Nullable String s) {
+ writeShort(createPropertyIndex(name));
+ writeString(s);
+ }
+
+ /**
+ * Writes the given name as the property name, and leaves it to the callee
+ * to fill in value for this property.
+ */
+ public void addPropertyKey(@NonNull String name) {
+ writeShort(createPropertyIndex(name));
+ }
+
+ private short createPropertyIndex(@NonNull String name) {
+ Short index = mPropertyNames.get(name);
+ if (index == null) {
+ index = mPropertyId++;
+ mPropertyNames.put(name, index);
+ }
+
+ return index;
+ }
+
+ private void startPropertyMap() {
+ try {
+ mStream.write(SIG_MAP);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void endPropertyMap() {
+ writeShort(SIG_END_MAP);
+ }
+
+ private void writeBoolean(boolean v) {
+ try {
+ mStream.write(SIG_BOOLEAN);
+ mStream.write(v ? 1 : 0);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeShort(short s) {
+ try {
+ mStream.write(SIG_SHORT);
+ mStream.writeShort(s);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeInt(int i) {
+ try {
+ mStream.write(SIG_INT);
+ mStream.writeInt(i);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeFloat(float v) {
+ try {
+ mStream.write(SIG_FLOAT);
+ mStream.writeFloat(v);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+
+ private void writeString(@Nullable String s) {
+ if (s == null) {
+ s = "";
+ }
+
+ try {
+ mStream.write(SIG_STRING);
+ byte[] bytes = s.getBytes(mCharset);
+
+ short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
+ mStream.writeShort(len);
+
+ mStream.write(bytes, 0, len);
+ } catch (IOException e) {
+ // does not happen since the stream simply wraps a ByteArrayOutputStream
+ }
+ }
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e983910..2797b6e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -16,11 +16,11 @@
package android.view;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.Presentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
@@ -1119,6 +1119,15 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
/**
+ * Flag to force the status bar window to be visible all the time. If the bar is hidden when
+ * this flag is set it will be shown again and the bar will have a transparent background.
+ * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT = 0x00001000;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
@@ -2066,5 +2075,18 @@ public interface WindowManager extends ViewManager {
}
private CharSequence mTitle = "";
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("x", x);
+ encoder.addProperty("y", y);
+ encoder.addProperty("horizontalWeight", horizontalWeight);
+ encoder.addProperty("verticalWeight", verticalWeight);
+ encoder.addProperty("type", type);
+ encoder.addProperty("flags", flags);
+ }
}
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 7ab5aaa..a261aaf 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.Widget;
import android.content.Context;
@@ -43,6 +44,7 @@ import android.view.View;
import android.view.ViewAssistStructure;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -2576,4 +2578,18 @@ public class WebView extends AbsoluteLayout
super.onFinishTemporaryDetach();
mProvider.getViewDelegate().onFinishTemporaryDetach();
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ checkThread();
+ encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
+ encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
+ encoder.addProperty("webview:scale", mProvider.getScale());
+ encoder.addProperty("webview:title", mProvider.getTitle());
+ encoder.addProperty("webview:url", mProvider.getUrl());
+ encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
+ }
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c57a53a..9903b7e 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -18,6 +18,7 @@ package android.widget;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -56,6 +57,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
@@ -6330,6 +6332,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("list:viewType", viewType);
+ encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
+ encoder.addProperty("list:forceAdd", forceAdd);
+ }
}
/**
@@ -6912,6 +6924,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
+ encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
+ encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
+ encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
+ encoder.addProperty("list:stackFromBottom", isStackFromBottom());
+ encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
+
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ encoder.addPropertyKey("selectedView");
+ selectedView.encode(encoder);
+ }
+ }
+
/**
* Abstract positon scroller used to handle smooth scrolling.
*/
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index e0b0e1f..f08141c 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -61,6 +61,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
private static final String TAG = "ActionMenuPresenter";
private static final int ITEM_ANIMATION_DURATION = 150;
+ private static final boolean ACTIONBAR_ANIMATIONS_ENABLED = false;
private OverflowMenuButton mOverflowButton;
private boolean mReserveOverflow;
@@ -414,7 +415,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
@Override
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
- if (menuViewParent != null) {
+ if (menuViewParent != null && ACTIONBAR_ANIMATIONS_ENABLED) {
setupItemAnimations();
}
super.updateMenuView(cleared);
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index d6f2276..278a8fb 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -15,6 +15,7 @@
*/
package android.widget;
+import android.annotation.NonNull;
import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -28,6 +29,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.view.menu.ActionMenuItemView;
import com.android.internal.view.menu.MenuBuilder;
@@ -835,5 +837,17 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
super(width, height);
this.isOverflowButton = isOverflowButton;
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("layout:overFlowButton", isOverflowButton);
+ encoder.addProperty("layout:cellsUsed", cellsUsed);
+ encoder.addProperty("layout:extraPixels", extraPixels);
+ encoder.addProperty("layout:expandable", expandable);
+ encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset);
+ }
}
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 72cb0b5..54e3996 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.database.DataSetObserver;
@@ -29,6 +30,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -1245,4 +1247,16 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("scrolling:firstPosition", mFirstPosition);
+ encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
+ encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
+ encoder.addProperty("list:selectedPosition", mSelectedPosition);
+ encoder.addProperty("list:itemCount", mItemCount);
+ }
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 22e079c..6b4b2c7 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -16,6 +16,8 @@
package android.widget;
+import android.annotation.NonNull;
+import android.view.ViewHierarchyEncoder;
import com.android.internal.R;
import android.annotation.DrawableRes;
@@ -459,4 +461,11 @@ public class CheckedTextView extends TextView implements Checkable {
info.setCheckable(true);
info.setChecked(mChecked);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ super.encodeProperties(stream);
+ stream.addProperty("text:checked", isChecked());
+ }
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index f2afeeb..770077d 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -17,8 +17,10 @@
package android.widget;
import android.annotation.DrawableRes;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.PorterDuff;
+import android.view.ViewHierarchyEncoder;
import com.android.internal.R;
import android.content.Context;
@@ -530,9 +532,16 @@ public abstract class CompoundButton extends Button implements Checkable {
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
-
+
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ super.encodeProperties(stream);
+ stream.addProperty("checked", isChecked());
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 712fdba..86a100f 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -688,44 +688,101 @@ public class Editor {
private int getWordStart(int offset) {
// FIXME - For this and similar methods we're not doing anything to check if there's
// a LocaleSpan in the text, this may be something we should try handling or checking for.
- int retOffset = getWordIteratorWithText().getBeginning(offset);
- if (retOffset == BreakIterator.DONE) retOffset = offset;
+ int retOffset = getWordIteratorWithText().prevBoundary(offset);
+ if (isPunctBoundaryBehind(retOffset, true /* isStart */)) {
+ // If we're on a punctuation boundary we should continue to get the
+ // previous offset until we're not longer on a punctuation boundary.
+ retOffset = getWordIteratorWithText().prevBoundary(retOffset);
+ while (!isPunctBoundaryBehind(retOffset, false /* isStart */)
+ && retOffset != BreakIterator.DONE) {
+ retOffset = getWordIteratorWithText().prevBoundary(retOffset);
+ }
+ }
+ if (retOffset == BreakIterator.DONE) {
+ return offset;
+ }
return retOffset;
}
- private int getWordEnd(int offset, boolean includePunctuation) {
- int retOffset = getWordIteratorWithText().getEnd(offset);
+ private int getWordEnd(int offset) {
+ int retOffset = getWordIteratorWithText().nextBoundary(offset);
+ if (isPunctBoundaryForward(retOffset, true /* isStart */)) {
+ // If we're on a punctuation boundary we should continue to get the
+ // next offset until we're no longer on a punctuation boundary.
+ retOffset = getWordIteratorWithText().nextBoundary(retOffset);
+ while (!isPunctBoundaryForward(retOffset, false /* isStart */)
+ && retOffset != BreakIterator.DONE) {
+ retOffset = getWordIteratorWithText().nextBoundary(retOffset);
+ }
+ }
if (retOffset == BreakIterator.DONE) {
- retOffset = offset;
- } else if (includePunctuation) {
- retOffset = handlePunctuation(retOffset);
+ return offset;
}
return retOffset;
}
- private boolean isEndBoundary(int offset) {
- int thisEnd = getWordEnd(offset, false);
- return offset == thisEnd;
- }
+ /**
+ * Checks for punctuation boundaries for the provided offset and the
+ * previous character.
+ *
+ * @param offset The offset to check from.
+ * @param isStart Whether the boundary being checked for is at the start or
+ * end of a punctuation sequence.
+ * @return Whether this is a punctuation boundary.
+ */
+ private boolean isPunctBoundaryBehind(int offset, boolean isStart) {
+ CharSequence text = mTextView.getText();
+ if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) {
+ return false;
+ }
+ int cp = Character.codePointAt(text, offset);
+ int prevCp = Character.codePointBefore(text, offset);
- private boolean isStartBoundary(int offset) {
- int thisStart = getWordStart(offset);
- return thisStart == offset;
+ if (isPunctuation(cp)) {
+ // If it's the start, the current cp and the prev cp are
+ // punctuation. If it's at the end of a punctuation sequence the
+ // current is punctuation and the prev is not.
+ return isStart ? isPunctuation(prevCp) : !isPunctuation(prevCp);
+ }
+ return false;
}
- private int handlePunctuation(int offset) {
- // FIXME - Check with UX how repeated ending punctuation should be handled.
- // FIXME - Check with UX if / how we would handle non sentence ending characters.
- // FIXME - Consider punctuation in different languages.
+ /**
+ * Checks for punctuation boundaries for the provided offset and the next
+ * character.
+ *
+ * @param offset The offset to check from.
+ * @param isStart Whether the boundary being checked for is at the start or
+ * end of a punctuation sequence.
+ * @return Whether this is a punctuation boundary.
+ */
+ private boolean isPunctBoundaryForward(int offset, boolean isStart) {
CharSequence text = mTextView.getText();
- if (offset < text.length()) {
- int c = Character.codePointAt(text, offset);
- if (c == 0x002e /* period */|| c == 0x003f /* question mark */
- || c == 0x0021 /* exclamation mark */) {
- offset = Character.offsetByCodePoints(text, offset, 1);
- }
+ if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) {
+ return false;
+ }
+ int cp = Character.codePointBefore(text, offset);
+ int nextCpOffset = Math.min(offset + Character.charCount(cp), text.length() - 1);
+ int nextCp = Character.codePointBefore(text, nextCpOffset);
+
+ if (isPunctuation(cp)) {
+ // If it's the start, the current cp and the next cp are
+ // punctuation. If it's at the end of a punctuation sequence the
+ // current is punctuation and the next is not.
+ return isStart ? isPunctuation(nextCp) : !isPunctuation(nextCp);
}
- return offset;
+ return false;
+ }
+
+ private boolean isPunctuation(int cp) {
+ int type = Character.getType(cp);
+ return (type == Character.CONNECTOR_PUNCTUATION ||
+ type == Character.DASH_PUNCTUATION ||
+ type == Character.END_PUNCTUATION ||
+ type == Character.FINAL_QUOTE_PUNCTUATION ||
+ type == Character.INITIAL_QUOTE_PUNCTUATION ||
+ type == Character.OTHER_PUNCTUATION ||
+ type == Character.START_PUNCTUATION);
}
/**
@@ -788,7 +845,7 @@ public class Editor {
if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE ||
selectionStart == selectionEnd) {
// Possible when the word iterator does not properly handle the text's language
- long range = getCharRange(minOffset);
+ long range = getCharClusterRange(minOffset);
selectionStart = TextUtils.unpackRangeStartFromLong(range);
selectionEnd = TextUtils.unpackRangeEndFromLong(range);
}
@@ -831,29 +888,25 @@ public class Editor {
return mWordIteratorWithText;
}
- private long getCharRange(int offset) {
+ private int getNextCursorOffset(int offset, boolean findAfterGivenOffset) {
+ final Layout layout = mTextView.getLayout();
+ if (layout == null) return offset;
+ final CharSequence text = mTextView.getText();
+ final int nextOffset = layout.getPaint().getTextRunCursor(text, 0, text.length(),
+ layout.isRtlCharAt(offset) ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR,
+ offset, findAfterGivenOffset ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE);
+ return nextOffset == -1 ? offset : nextOffset;
+ }
+
+ private long getCharClusterRange(int offset) {
final int textLength = mTextView.getText().length();
- if (offset + 1 < textLength) {
- final char currentChar = mTextView.getText().charAt(offset);
- final char nextChar = mTextView.getText().charAt(offset + 1);
- if (Character.isSurrogatePair(currentChar, nextChar)) {
- return TextUtils.packRangeInLong(offset, offset + 2);
- }
- }
if (offset < textLength) {
- return TextUtils.packRangeInLong(offset, offset + 1);
- }
- if (offset - 2 >= 0) {
- final char previousChar = mTextView.getText().charAt(offset - 1);
- final char previousPreviousChar = mTextView.getText().charAt(offset - 2);
- if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
- return TextUtils.packRangeInLong(offset - 2, offset);
- }
+ return TextUtils.packRangeInLong(offset, getNextCursorOffset(offset, true));
}
if (offset - 1 >= 0) {
- return TextUtils.packRangeInLong(offset - 1, offset);
+ return TextUtils.packRangeInLong(getNextCursorOffset(offset, false), offset);
}
- return TextUtils.packRangeInLong(offset, offset);
+ return TextUtils.packRangeInLong(offset, offset);
}
private boolean touchPositionIsInSelection() {
@@ -3903,13 +3956,9 @@ public class Editor {
public void updatePosition(float x, float y) {
final int trueOffset = mTextView.getOffsetForPosition(x, y);
final int currLine = mTextView.getLineAtCoordinate(y);
-
- // Don't select white space on different lines.
- if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return;
-
boolean positionCursor = false;
int offset = trueOffset;
- int end = getWordEnd(offset, true);
+ int end = getWordEnd(offset);
int start = getWordStart(offset);
if (offset < mPreviousOffset) {
@@ -3925,7 +3974,7 @@ public class Editor {
}
}
mTouchWordOffset = Math.max(trueOffset - offset, 0);
- mInWord = !isStartBoundary(offset);
+ mInWord = !getWordIteratorWithText().isBoundary(offset);
positionCursor = true;
} else if (offset - mTouchWordOffset > mPreviousOffset) {
// User is shrinking the selection.
@@ -3934,7 +3983,7 @@ public class Editor {
offset = end;
}
offset -= mTouchWordOffset;
- mInWord = !isEndBoundary(offset);
+ mInWord = !getWordIteratorWithText().isBoundary(offset);
positionCursor = true;
}
@@ -3946,7 +3995,7 @@ public class Editor {
int alteredOffset = mTextView.getOffsetAtCoordinate(mPrevLine, x);
if (alteredOffset >= selectionEnd) {
// Can't pass the other drag handle.
- offset = Math.max(0, selectionEnd - 1);
+ offset = getNextCursorOffset(selectionEnd, false);
} else {
offset = alteredOffset;
}
@@ -4005,14 +4054,9 @@ public class Editor {
public void updatePosition(float x, float y) {
final int trueOffset = mTextView.getOffsetForPosition(x, y);
final int currLine = mTextView.getLineAtCoordinate(y);
-
- // Don't select white space on different lines.
- if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return;
-
int offset = trueOffset;
boolean positionCursor = false;
-
- int end = getWordEnd(offset, true);
+ int end = getWordEnd(offset);
int start = getWordStart(offset);
if (offset > mPreviousOffset) {
@@ -4028,7 +4072,7 @@ public class Editor {
}
}
mTouchWordOffset = Math.max(offset - trueOffset, 0);
- mInWord = !isEndBoundary(offset);
+ mInWord = !getWordIteratorWithText().isBoundary(offset);
positionCursor = true;
} else if (offset + mTouchWordOffset < mPreviousOffset) {
// User is shrinking the selection.
@@ -4038,7 +4082,7 @@ public class Editor {
}
offset += mTouchWordOffset;
positionCursor = true;
- mInWord = !isStartBoundary(offset);
+ mInWord = !getWordIteratorWithText().isBoundary(offset);
}
if (positionCursor) {
@@ -4049,7 +4093,7 @@ public class Editor {
int length = mTextView.getText().length();
if (alteredOffset <= selectionStart) {
// Can't pass the other drag handle.
- offset = Math.min(selectionStart + 1, length);
+ offset = getNextCursorOffset(selectionStart, true);
} else {
offset = Math.min(alteredOffset, length);
}
@@ -4070,36 +4114,6 @@ public class Editor {
}
/**
- * Checks whether selection is happening on a different line than previous and
- * if that line only contains whitespace up to the touch location.
- *
- * @param prevLine The previous line the selection was on.
- * @param currLine The current line being selected.
- * @param offset The offset in the text where the touch occurred.
- * @return Whether or not it was just a white space line being selected.
- */
- private boolean isWhitespaceLine(int prevLine, int currLine, int offset) {
- if (prevLine == currLine) {
- // Same line; don't care.
- return false;
- }
- CharSequence text = mTextView.getText();
- if (offset == text.length()) {
- // No character at the last position.
- return false;
- }
- int lineEndOffset = mTextView.getLayout().getLineEnd(currLine);
- for (int cp, i = offset; i < lineEndOffset; i += Character.charCount(cp)) {
- cp = Character.codePointAt(text, i);
- if (!Character.isSpaceChar(cp) && !Character.isWhitespace(cp)) {
- // There are non white space chars on the line.
- return false;
- }
- }
- return true;
- }
-
- /**
* A CursorController instance can be used to control a cursor in the text.
*/
private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
@@ -4178,8 +4192,6 @@ public class Editor {
private int mStartOffset = -1;
// Indicates whether the user is selecting text and using the drag accelerator.
private boolean mDragAcceleratorActive;
- // Indicates the line of text the drag accelerator is on.
- private int mPrevLine = -1;
SelectionModifierCursorController() {
resetTouchOffsets();
@@ -4272,8 +4284,6 @@ public class Editor {
}
}
- // New selection, reset line.
- mPrevLine = mTextView.getLineAtCoordinate(y);
mDownPositionX = x;
mDownPositionY = y;
mGestureStayedInTapRegion = true;
@@ -4318,7 +4328,7 @@ public class Editor {
// We don't start "dragging" until the user is past the initial word that
// gets selected on long press.
int firstWordStart = getWordStart(mStartOffset);
- int firstWordEnd = getWordEnd(mStartOffset, false);
+ int firstWordEnd = getWordEnd(mStartOffset);
if (offset > firstWordEnd || offset < firstWordStart) {
// Basically the goal in the below code is to have the highlight be
@@ -4330,13 +4340,6 @@ public class Editor {
if (my > fingerOffset) my -= fingerOffset;
offset = mTextView.getOffsetForPosition(mx, my);
- int currLine = mTextView.getLineAtCoordinate(my);
-
- // Don't select white space on different lines.
- if (isWhitespaceLine(mPrevLine, currLine, offset)) return;
-
- mPrevLine = currLine;
-
// Perform the check for closeness at edge of view, if we're very close
// don't adjust the offset to be in front of the finger - otherwise the
// user can't select words at the edge.
@@ -4365,7 +4368,7 @@ public class Editor {
// Need to adjust start offset based on direction of movement.
int newStart = mStartOffset < offset ? getWordStart(mStartOffset)
- : getWordEnd(mStartOffset, true);
+ : getWordEnd(mStartOffset);
Selection.setSelection((Spannable) mTextView.getText(), newStart,
offset);
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 0602944..7ca450a 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -33,6 +33,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
@@ -407,6 +408,18 @@ public class FrameLayout extends ViewGroup {
return FrameLayout.class.getName();
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("measurement:measureAllChildren", mMeasureAllChildren);
+ encoder.addProperty("padding:foregroundPaddingLeft", mForegroundPaddingLeft);
+ encoder.addProperty("padding:foregroundPaddingTop", mForegroundPaddingTop);
+ encoder.addProperty("padding:foregroundPaddingRight", mForegroundPaddingRight);
+ encoder.addProperty("padding:foregroundPaddingBottom", mForegroundPaddingBottom);
+ }
+
/**
* Per-child layout information for layouts that support margins.
* See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index c959774..dcaafa5 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -31,6 +32,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -2420,4 +2422,11 @@ public class GridView extends AbsListView {
row, 1, column, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("numColumns", getNumColumns());
+ }
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 0879c5d..cf67905 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -35,6 +36,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -1695,6 +1697,13 @@ public class HorizontalScrollView extends FrameLayout {
return ss;
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("layout:fillViewPort", mFillViewport);
+ }
+
static class SavedState extends BaseSavedState {
public int scrollPosition;
public boolean isLayoutRtl;
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 6d2f368..05059bc 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.DrawableRes;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,6 +44,7 @@ import android.util.Log;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
@@ -1431,4 +1433,11 @@ public class ImageView extends View {
public CharSequence getAccessibilityClassName() {
return ImageView.class.getName();
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ super.encodeProperties(stream);
+ stream.addProperty("layout:baseline", getBaseline());
+ }
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 72f51c9..f153ce5 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -19,6 +19,7 @@ package android.widget;
import com.android.internal.R;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
@@ -29,6 +30,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
import java.lang.annotation.Retention;
@@ -1813,6 +1815,20 @@ public class LinearLayout extends ViewGroup {
return LinearLayout.class.getName();
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("layout:baselineAligned", mBaselineAligned);
+ encoder.addProperty("layout:baselineAlignedChildIndex", mBaselineAlignedChildIndex);
+ encoder.addProperty("measurement:baselineChildTop", mBaselineChildTop);
+ encoder.addProperty("measurement:orientation", mOrientation);
+ encoder.addProperty("measurement:gravity", mGravity);
+ encoder.addProperty("measurement:totalLength", mTotalLength);
+ encoder.addProperty("layout:totalLength", mTotalLength);
+ encoder.addProperty("layout:useLargestChild", mUseLargestChild);
+ }
+
/**
* Per-child layout information associated with ViewLinearLayout.
*
@@ -1921,5 +1937,14 @@ public class LinearLayout extends ViewGroup {
return output + "LinearLayout.LayoutParams={width=" + sizeToString(width) +
", height=" + sizeToString(height) + " weight=" + weight + "}";
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("layout:weight", weight);
+ encoder.addProperty("layout:gravity", gravity);
+ }
}
}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 05866f0..94b9416 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -1791,8 +1791,9 @@ public class ListPopupWindow {
private class ResizePopupRunnable implements Runnable {
public void run() {
- if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
- mDropDownList.getChildCount() <= mListItemExpandMaximum) {
+ if (mDropDownList != null && mDropDownList.isAttachedToWindow()
+ && mDropDownList.getCount() > mDropDownList.getChildCount()
+ && mDropDownList.getChildCount() <= mListItemExpandMaximum) {
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
show();
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index a79c8e8..7dcaa1f 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -23,6 +23,7 @@ import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;
import android.annotation.IdRes;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -40,6 +41,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -3938,4 +3940,12 @@ public class ListView extends AbsListView {
position, 1, 0, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("recycleOnMeasure", recycleOnMeasure());
+ }
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index b59ae17..639a09c 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.PorterDuff;
@@ -49,6 +50,7 @@ import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AlphaAnimation;
@@ -1893,6 +1895,17 @@ public class ProgressBar extends View {
postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ super.encodeProperties(stream);
+
+ stream.addProperty("progress:max", getMax());
+ stream.addProperty("progress:progress", getProgress());
+ stream.addProperty("progress:secondaryProgress", getSecondaryProgress());
+ stream.addProperty("progress:indeterminate", isIndeterminate());
+ }
+
/**
* Command for sending an accessibility event.
*/
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index d12739f..affc5da 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.util.ArrayMap;
import com.android.internal.R;
@@ -36,6 +37,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
@@ -1616,6 +1618,13 @@ public class RelativeLayout extends ViewGroup {
// This will set the layout direction
super.resolveLayoutDirection(layoutDirection);
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("layout:alignWithParent", alignWithParent);
+ }
}
private static class DependencyGraph {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 98d61d3..2709f25 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -38,6 +39,7 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -1787,6 +1789,13 @@ public class ScrollView extends FrameLayout {
return ss;
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("fillViewport", mFillViewport);
+ }
+
static class SavedState extends BaseSavedState {
public int scrollPosition;
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index f73ee49..d4288d6 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -24,7 +25,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-
+import android.view.ViewHierarchyEncoder;
/**
* <p>A layout that arranges its children horizontally. A TableRow should
@@ -509,6 +510,14 @@ public class TableRow extends LinearLayout {
height = WRAP_CONTENT;
}
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("layout:column", column);
+ encoder.addProperty("layout:span", span);
+ }
}
// special transparent hierarchy change listener
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index e2acaac..5d7b569 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -32,6 +33,7 @@ import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
+import android.view.ViewHierarchyEncoder;
import com.android.internal.R;
@@ -546,4 +548,18 @@ public class TextClock extends TextView {
mTime.setTimeInMillis(System.currentTimeMillis());
setText(DateFormat.format(mFormat, mTime));
}
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ super.encodeProperties(stream);
+
+ CharSequence s = getFormat12Hour();
+ stream.addProperty("format12Hour", s == null ? null : s.toString());
+
+ s = getFormat24Hour();
+ stream.addProperty("format24Hour", s == null ? null : s.toString());
+ stream.addProperty("format", mFormat == null ? null : mFormat.toString());
+ stream.addProperty("hasSeconds", mHasSeconds);
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 449173f..b9a08f5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -120,6 +120,7 @@ import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
+import android.view.ViewHierarchyEncoder;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -2844,7 +2845,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
})
public int getTypefaceStyle() {
- return mTextPaint.getTypeface().getStyle();
+ Typeface typeface = mTextPaint.getTypeface();
+ return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
}
/**
@@ -9556,6 +9558,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
+ super.encodeProperties(stream);
+
+ TruncateAt ellipsize = getEllipsize();
+ stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
+ stream.addProperty("text:textSize", getTextSize());
+ stream.addProperty("text:scaledTextSize", getScaledTextSize());
+ stream.addProperty("text:typefaceStyle", getTypefaceStyle());
+ stream.addProperty("text:selectionStart", getSelectionStart());
+ stream.addProperty("text:selectionEnd", getSelectionEnd());
+ stream.addProperty("text:curTextColor", mCurTextColor);
+ stream.addProperty("text:text", mText == null ? null : mText.toString());
+ stream.addProperty("text:gravity", mGravity);
+ }
+
/**
* User interface state that is stored by TextView for implementing
* {@link View#onSaveInstanceState}.
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 6173832..9277f9b 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -18,6 +18,7 @@ package com.android.internal.logging;
import android.content.Context;
import android.os.Build;
+import android.view.View;
/**
* Log all the things.
@@ -33,6 +34,10 @@ public class MetricsLogger implements MetricsConstants {
public static final int ACTION_BAN_APP_NOTES = 146;
public static final int NOTIFICATION_ZEN_MODE_EVENT_RULE = 147;
public static final int ACTION_DISMISS_ALL_NOTES = 148;
+ public static final int QS_DND_DETAILS = 149;
+ public static final int QS_BLUETOOTH_DETAILS = 150;
+ public static final int QS_CAST_DETAILS = 151;
+ public static final int QS_WIFI_DETAILS = 152;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
@@ -41,13 +46,27 @@ public class MetricsLogger implements MetricsConstants {
EventLogTags.writeSysuiViewVisibility(category, 100);
}
- public static void hidden(Context context, int category) {
+ public static void hidden(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
throw new IllegalArgumentException("Must define metric category");
}
EventLogTags.writeSysuiViewVisibility(category, 0);
}
+ public static void visibility(Context context, int category, boolean visibile)
+ throws IllegalArgumentException {
+ if (visibile) {
+ visible(context, category);
+ } else {
+ hidden(context, category);
+ }
+ }
+
+ public static void visibility(Context context, int category, int vis)
+ throws IllegalArgumentException {
+ visibility(context, category, vis == View.VISIBLE);
+ }
+
public static void action(Context context, int category) {
action(context, category, "");
}
diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java
deleted file mode 100644
index 1a6736a..0000000
--- a/core/java/com/android/internal/transition/EpicenterClipReveal.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2015 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.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.RectEvaluator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.transition.TransitionValues;
-import android.transition.Visibility;
-import android.util.AttributeSet;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.animation.PathInterpolator;
-
-import com.android.internal.R;
-
-/**
- * EpicenterClipReveal captures the {@link View#getClipBounds()} before and
- * after the scene change and animates between those and the epicenter bounds
- * during a visibility transition.
- */
-public class EpicenterClipReveal extends Visibility {
- private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
- private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
-
- private final TimeInterpolator mInterpolatorX;
- private final TimeInterpolator mInterpolatorY;
- private final boolean mCenterClipBounds;
-
- public EpicenterClipReveal() {
- mInterpolatorX = null;
- mInterpolatorY = null;
- mCenterClipBounds = false;
- }
-
- public EpicenterClipReveal(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.EpicenterClipReveal, 0, 0);
-
- mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false);
-
- final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0);
- if (interpolatorX != 0) {
- mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
- } else {
- mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN;
- }
-
- final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0);
- if (interpolatorY != 0) {
- mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
- } else {
- mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
- }
-
- a.recycle();
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- super.captureStartValues(transitionValues);
- captureValues(transitionValues);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- super.captureEndValues(transitionValues);
- captureValues(transitionValues);
- }
-
- private void captureValues(TransitionValues values) {
- final View view = values.view;
- if (view.getVisibility() == View.GONE) {
- return;
- }
-
- final Rect clip = view.getClipBounds();
- values.values.put(PROPNAME_CLIP, clip);
-
- if (clip == null) {
- final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
- values.values.put(PROPNAME_BOUNDS, bounds);
- }
- }
-
- @Override
- public Animator onAppear(ViewGroup sceneRoot, View view,
- TransitionValues startValues, TransitionValues endValues) {
- if (endValues == null) {
- return null;
- }
-
- final Rect end = getBestRect(endValues);
- final Rect start = getEpicenterOrCenter(end);
-
- // Prepare the view.
- view.setClipBounds(start);
-
- return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY);
- }
-
- @Override
- public Animator onDisappear(ViewGroup sceneRoot, View view,
- TransitionValues startValues, TransitionValues endValues) {
- if (startValues == null) {
- return null;
- }
-
- final Rect start = getBestRect(startValues);
- final Rect end = getEpicenterOrCenter(start);
-
- // Prepare the view.
- view.setClipBounds(start);
-
- return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY);
- }
-
- private Rect getEpicenterOrCenter(Rect bestRect) {
- final Rect epicenter = getEpicenter();
- if (epicenter != null) {
- // Translate the clip bounds to be centered within the target bounds.
- if (mCenterClipBounds) {
- final int offsetX = bestRect.centerX() - epicenter.centerX();
- final int offsetY = bestRect.centerY() - epicenter.centerY();
- epicenter.offset(offsetX, offsetY);
- }
- return epicenter;
- }
-
- final int centerX = bestRect.centerX();
- final int centerY = bestRect.centerY();
- return new Rect(centerX, centerY, centerX, centerY);
- }
-
- private Rect getBestRect(TransitionValues values) {
- final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP);
- if (clipRect == null) {
- return (Rect) values.values.get(PROPNAME_BOUNDS);
- }
- return clipRect;
- }
-
- private static Animator createRectAnimator(final View view, Rect start, Rect end,
- TransitionValues endValues, TimeInterpolator interpolatorX,
- TimeInterpolator interpolatorY) {
- final RectEvaluator evaluator = new RectEvaluator(new Rect());
- final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
-
- final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X);
- final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end);
- if (interpolatorX != null) {
- animX.setInterpolator(interpolatorX);
- }
-
- final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y);
- final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end);
- if (interpolatorY != null) {
- animY.setInterpolator(interpolatorY);
- }
-
- final AnimatorSet animSet = new AnimatorSet();
- animSet.playTogether(animX, animY);
- animSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setClipBounds(terminalClip);
- }
- });
- return animSet;
- }
-
- private static class ClipDimenProperty extends Property<View, Rect> {
- public static final char TARGET_X = 'x';
- public static final char TARGET_Y = 'y';
-
- private final Rect mTempRect = new Rect();
-
- private final int mTargetDimension;
-
- public ClipDimenProperty(char targetDimension) {
- super(Rect.class, "clip_bounds_" + targetDimension);
-
- mTargetDimension = targetDimension;
- }
-
- @Override
- public Rect get(View object) {
- final Rect tempRect = mTempRect;
- if (!object.getClipBounds(tempRect)) {
- tempRect.setEmpty();
- }
- return tempRect;
- }
-
- @Override
- public void set(View object, Rect value) {
- final Rect tempRect = mTempRect;
- if (object.getClipBounds(tempRect)) {
- if (mTargetDimension == TARGET_X) {
- tempRect.left = value.left;
- tempRect.right = value.right;
- } else {
- tempRect.top = value.top;
- tempRect.bottom = value.bottom;
- }
- object.setClipBounds(tempRect);
- }
- }
- }
-}
diff --git a/core/java/com/android/internal/transition/EpicenterTranslate.java b/core/java/com/android/internal/transition/EpicenterTranslate.java
deleted file mode 100644
index eac3ff8..0000000
--- a/core/java/com/android/internal/transition/EpicenterTranslate.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2015 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.transition;
-
-import com.android.internal.R;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.transition.TransitionValues;
-import android.transition.Visibility;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-
-/**
- * EpicenterTranslate captures the {@link View#getTranslationX()} and
- * {@link View#getTranslationY()} before and after the scene change and
- * animates between those and the epicenter's center during a visibility
- * transition.
- */
-public class EpicenterTranslate extends Visibility {
- private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
- private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX";
- private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY";
- private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ";
- private static final String PROPNAME_Z = "android:epicenterReveal:z";
-
- private final TimeInterpolator mInterpolatorX;
- private final TimeInterpolator mInterpolatorY;
- private final TimeInterpolator mInterpolatorZ;
-
- public EpicenterTranslate() {
- mInterpolatorX = null;
- mInterpolatorY = null;
- mInterpolatorZ = null;
- }
-
- public EpicenterTranslate(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.EpicenterTranslate, 0, 0);
-
- final int interpolatorX = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorX, 0);
- if (interpolatorX != 0) {
- mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
- } else {
- mInterpolatorX = TransitionConstants.FAST_OUT_SLOW_IN;
- }
-
- final int interpolatorY = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorY, 0);
- if (interpolatorY != 0) {
- mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
- } else {
- mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
- }
-
- final int interpolatorZ = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorZ, 0);
- if (interpolatorZ != 0) {
- mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ);
- } else {
- mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN;
- }
-
- a.recycle();
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- super.captureStartValues(transitionValues);
- captureValues(transitionValues);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- super.captureEndValues(transitionValues);
- captureValues(transitionValues);
- }
-
- private void captureValues(TransitionValues values) {
- final View view = values.view;
- if (view.getVisibility() == View.GONE) {
- return;
- }
-
- final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
- values.values.put(PROPNAME_BOUNDS, bounds);
- values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX());
- values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY());
- values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ());
- values.values.put(PROPNAME_Z, view.getZ());
- }
-
- @Override
- public Animator onAppear(ViewGroup sceneRoot, View view,
- TransitionValues startValues, TransitionValues endValues) {
- if (endValues == null) {
- return null;
- }
-
- final Rect end = (Rect) endValues.values.get(PROPNAME_BOUNDS);
- final Rect start = getEpicenterOrCenter(end);
- final float startX = start.centerX() - end.centerX();
- final float startY = start.centerY() - end.centerY();
- final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z);
-
- // Translate the view to be centered on the epicenter.
- view.setTranslationX(startX);
- view.setTranslationY(startY);
- view.setTranslationZ(startZ);
-
- final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
- final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
- final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
- return createAnimator(view, startX, startY, startZ, endX, endY, endZ,
- mInterpolatorX, mInterpolatorY, mInterpolatorZ);
- }
-
- @Override
- public Animator onDisappear(ViewGroup sceneRoot, View view,
- TransitionValues startValues, TransitionValues endValues) {
- if (startValues == null) {
- return null;
- }
-
- final Rect start = (Rect) endValues.values.get(PROPNAME_BOUNDS);
- final Rect end = getEpicenterOrCenter(start);
- final float endX = end.centerX() - start.centerX();
- final float endY = end.centerY() - start.centerY();
- final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z);
-
- final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
- final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
- final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
- return createAnimator(view, startX, startY, startZ, endX, endY, endZ,
- mInterpolatorX, mInterpolatorY, mInterpolatorZ);
- }
-
- private Rect getEpicenterOrCenter(Rect bestRect) {
- final Rect epicenter = getEpicenter();
- if (epicenter != null) {
- return epicenter;
- }
-
- final int centerX = bestRect.centerX();
- final int centerY = bestRect.centerY();
- return new Rect(centerX, centerY, centerX, centerY);
- }
-
- private static Animator createAnimator(final View view, float startX, float startY,
- float startZ, float endX, float endY, float endZ, TimeInterpolator interpolatorX,
- TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ) {
- final ObjectAnimator animX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX);
- if (interpolatorX != null) {
- animX.setInterpolator(interpolatorX);
- }
-
- final ObjectAnimator animY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY);
- if (interpolatorY != null) {
- animY.setInterpolator(interpolatorY);
- }
-
- final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ);
- if (interpolatorZ != null) {
- animZ.setInterpolator(interpolatorZ);
- }
-
- final AnimatorSet animSet = new AnimatorSet();
- animSet.playTogether(animX, animY, animZ);
- return animSet;
- }
-}
diff --git a/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java
new file mode 100644
index 0000000..2c10297
--- /dev/null
+++ b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2015 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.TypeEvaluator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+/**
+ * EpicenterTranslateClipReveal captures the clip bounds and translation values
+ * before and after the scene change and animates between those and the
+ * epicenter bounds during a visibility transition.
+ */
+public class EpicenterTranslateClipReveal extends Visibility {
+ private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
+ private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
+ private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX";
+ private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY";
+ private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ";
+ private static final String PROPNAME_Z = "android:epicenterReveal:z";
+
+ private final TimeInterpolator mInterpolatorX;
+ private final TimeInterpolator mInterpolatorY;
+ private final TimeInterpolator mInterpolatorZ;
+
+ public EpicenterTranslateClipReveal() {
+ mInterpolatorX = null;
+ mInterpolatorY = null;
+ mInterpolatorZ = null;
+ }
+
+ public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.EpicenterTranslateClipReveal, 0, 0);
+
+ final int interpolatorX = a.getResourceId(
+ R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0);
+ if (interpolatorX != 0) {
+ mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
+ } else {
+ mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN;
+ }
+
+ final int interpolatorY = a.getResourceId(
+ R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0);
+ if (interpolatorY != 0) {
+ mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
+ } else {
+ mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
+ }
+
+ final int interpolatorZ = a.getResourceId(
+ R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0);
+ if (interpolatorZ != 0) {
+ mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ);
+ } else {
+ mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN;
+ }
+
+ a.recycle();
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ private void captureValues(TransitionValues values) {
+ final View view = values.view;
+ if (view.getVisibility() == View.GONE) {
+ return;
+ }
+
+ final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+ values.values.put(PROPNAME_BOUNDS, bounds);
+ values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX());
+ values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY());
+ values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ());
+ values.values.put(PROPNAME_Z, view.getZ());
+
+ final Rect clip = view.getClipBounds();
+ values.values.put(PROPNAME_CLIP, clip);
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+
+ final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+ final Rect startBounds = getEpicenterOrCenter(endBounds);
+ final float startX = startBounds.centerX() - endBounds.centerX();
+ final float startY = startBounds.centerY() - endBounds.centerY();
+ final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z);
+
+ // Translate the view to be centered on the epicenter.
+ view.setTranslationX(startX);
+ view.setTranslationY(startY);
+ view.setTranslationZ(startZ);
+
+ final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
+ final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
+ final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
+
+ final Rect endClip = getBestRect(endValues);
+ final Rect startClip = getEpicenterOrCenter(endClip);
+
+ // Prepare the view.
+ view.setClipBounds(startClip);
+
+ final State startStateX = new State(startClip.left, startClip.right, startX);
+ final State endStateX = new State(endClip.left, endClip.right, endX);
+ final State startStateY = new State(startClip.top, startClip.bottom, startY);
+ final State endStateY = new State(endClip.top, endClip.bottom, endY);
+
+ return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY,
+ endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null) {
+ return null;
+ }
+
+ final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+ final Rect endBounds = getEpicenterOrCenter(startBounds);
+ final float endX = endBounds.centerX() - startBounds.centerX();
+ final float endY = endBounds.centerY() - startBounds.centerY();
+ final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z);
+
+ final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
+ final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
+ final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
+
+ final Rect startClip = getBestRect(startValues);
+ final Rect endClip = getEpicenterOrCenter(startClip);
+
+ // Prepare the view.
+ view.setClipBounds(startClip);
+
+ final State startStateX = new State(startClip.left, startClip.right, startX);
+ final State endStateX = new State(endClip.left, endClip.right, endX);
+ final State startStateY = new State(startClip.top, startClip.bottom, startY);
+ final State endStateY = new State(endClip.top, endClip.bottom, endY);
+
+ return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY,
+ endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ);
+ }
+
+ private Rect getEpicenterOrCenter(Rect bestRect) {
+ final Rect epicenter = getEpicenter();
+ if (epicenter != null) {
+ return epicenter;
+ }
+
+ final int centerX = bestRect.centerX();
+ final int centerY = bestRect.centerY();
+ return new Rect(centerX, centerY, centerX, centerY);
+ }
+
+ private Rect getBestRect(TransitionValues values) {
+ final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP);
+ if (clipRect == null) {
+ return (Rect) values.values.get(PROPNAME_BOUNDS);
+ }
+ return clipRect;
+ }
+
+ private static Animator createRectAnimator(final View view, State startX, State startY,
+ float startZ, State endX, State endY, float endZ, TransitionValues endValues,
+ TimeInterpolator interpolatorX, TimeInterpolator interpolatorY,
+ TimeInterpolator interpolatorZ) {
+ final StateEvaluator evaluator = new StateEvaluator();
+
+ final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ);
+ if (interpolatorZ != null) {
+ animZ.setInterpolator(interpolatorZ);
+ }
+
+ final StateProperty propX = new StateProperty(StateProperty.TARGET_X);
+ final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX);
+ if (interpolatorX != null) {
+ animX.setInterpolator(interpolatorX);
+ }
+
+ final StateProperty propY = new StateProperty(StateProperty.TARGET_Y);
+ final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY);
+ if (interpolatorY != null) {
+ animY.setInterpolator(interpolatorY);
+ }
+
+ final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+ final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setClipBounds(terminalClip);
+ }
+ };
+
+ final AnimatorSet animSet = new AnimatorSet();
+ animSet.playTogether(animX, animY, animZ);
+ animSet.addListener(animatorListener);
+ return animSet;
+ }
+
+ private static class State {
+ int lower;
+ int upper;
+ float trans;
+
+ public State() {}
+
+ public State(int lower, int upper, float trans) {
+ this.lower = lower;
+ this.upper = upper;
+ this.trans = trans;
+ }
+ }
+
+ private static class StateEvaluator implements TypeEvaluator<State> {
+ private final State mTemp = new State();
+
+ @Override
+ public State evaluate(float fraction, State startValue, State endValue) {
+ mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction);
+ mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction);
+ mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction);
+ return mTemp;
+ }
+ }
+
+ private static class StateProperty extends Property<View, State> {
+ public static final char TARGET_X = 'x';
+ public static final char TARGET_Y = 'y';
+
+ private final Rect mTempRect = new Rect();
+ private final State mTempState = new State();
+
+ private final int mTargetDimension;
+
+ public StateProperty(char targetDimension) {
+ super(State.class, "state_" + targetDimension);
+
+ mTargetDimension = targetDimension;
+ }
+
+ @Override
+ public State get(View object) {
+ final Rect tempRect = mTempRect;
+ if (!object.getClipBounds(tempRect)) {
+ tempRect.setEmpty();
+ }
+ final State tempState = mTempState;
+ if (mTargetDimension == TARGET_X) {
+ tempState.trans = object.getTranslationX();
+ tempState.lower = tempRect.left + (int) tempState.trans;
+ tempState.upper = tempRect.right + (int) tempState.trans;
+ } else {
+ tempState.trans = object.getTranslationY();
+ tempState.lower = tempRect.top + (int) tempState.trans;
+ tempState.upper = tempRect.bottom + (int) tempState.trans;
+ }
+ return tempState;
+ }
+
+ @Override
+ public void set(View object, State value) {
+ final Rect tempRect = mTempRect;
+ if (object.getClipBounds(tempRect)) {
+ if (mTargetDimension == TARGET_X) {
+ tempRect.left = value.lower - (int) value.trans;
+ tempRect.right = value.upper - (int) value.trans;
+ } else {
+ tempRect.top = value.lower - (int) value.trans;
+ tempRect.bottom = value.upper - (int) value.trans;
+ }
+ object.setClipBounds(tempRect);
+ }
+
+ if (mTargetDimension == TARGET_X) {
+ object.setTranslationX(value.trans);
+ } else {
+ object.setTranslationY(value.trans);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java
index 34f62ba..bd0e6ce 100644
--- a/core/java/com/android/internal/util/AsyncChannel.java
+++ b/core/java/com/android/internal/util/AsyncChannel.java
@@ -462,10 +462,8 @@ public class AsyncChannel {
} catch(Exception e) {
}
// Tell source we're disconnected.
- if (mSrcHandler != null) {
- replyDisconnected(STATUS_SUCCESSFUL);
- mSrcHandler = null;
- }
+ replyDisconnected(STATUS_SUCCESSFUL);
+ mSrcHandler = null;
// Unlink only when bindService isn't used
if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) {
mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0);
@@ -871,6 +869,8 @@ public class AsyncChannel {
* @param status to be stored in msg.arg1
*/
private void replyDisconnected(int status) {
+ // Can't reply if already disconnected. Avoid NullPointerException.
+ if (mSrcHandler == null) return;
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
msg.arg1 = status;
msg.obj = this;
diff --git a/core/java/com/android/internal/util/CallbackRegistry.java b/core/java/com/android/internal/util/CallbackRegistry.java
new file mode 100644
index 0000000..0f228d4
--- /dev/null
+++ b/core/java/com/android/internal/util/CallbackRegistry.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks callbacks for the event. This class supports reentrant modification
+ * of the callbacks during notification without adversely disrupting notifications.
+ * A common pattern for callbacks is to receive a notification and then remove
+ * themselves. This class handles this behavior with constant memory under
+ * most circumstances.
+ *
+ * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to
+ * the constructor to define how notifications should be called. That implementation
+ * does the actual notification on the listener.</p>
+ *
+ * <p>This class supports only callbacks with at most two parameters.
+ * Typically, these are the notification originator and a parameter, but these may
+ * be used as required. If more than two parameters are required or primitive types
+ * must be used, <code>A</code> should be some kind of containing structure that
+ * the subclass may reuse between notifications.</p>
+ *
+ * @param <C> The callback type.
+ * @param <T> The notification sender type. Typically this is the containing class.
+ * @param <A> Opaque argument used to pass additional data beyond an int.
+ */
+public class CallbackRegistry<C, T, A> implements Cloneable {
+ private static final String TAG = "CallbackRegistry";
+
+ /** An ordered collection of listeners waiting to be notified. */
+ private List<C> mCallbacks = new ArrayList<C>();
+
+ /**
+ * A bit flag for the first 64 listeners that are removed during notification.
+ * The lowest significant bit corresponds to the 0th index into mCallbacks.
+ * For a small number of callbacks, no additional array of objects needs to
+ * be allocated.
+ */
+ private long mFirst64Removed = 0x0;
+
+ /**
+ * Bit flags for the remaining callbacks that are removed during notification.
+ * When there are more than 64 callbacks and one is marked for removal, a dynamic
+ * array of bits are allocated for the callbacks.
+ */
+ private long[] mRemainderRemoved;
+
+ /**
+ * The reentrancy level of the notification. When we notify a callback, it may cause
+ * further notifications. The reentrancy level must be tracked to let us clean up
+ * the callback state when all notifications have been processed.
+ */
+ private int mNotificationLevel;
+
+ /** The notification mechanism for notifying an event. */
+ private final NotifierCallback<C, T, A> mNotifier;
+
+ /**
+ * Creates an EventRegistry that notifies the event with notifier.
+ * @param notifier The class to use to notify events.
+ */
+ public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
+ mNotifier = notifier;
+ }
+
+ /**
+ * Notify all callbacks.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ */
+ public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
+ mNotificationLevel++;
+ notifyRecurseLocked(sender, arg, arg2);
+ mNotificationLevel--;
+ if (mNotificationLevel == 0) {
+ if (mRemainderRemoved != null) {
+ for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
+ final long removedBits = mRemainderRemoved[i];
+ if (removedBits != 0) {
+ removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
+ mRemainderRemoved[i] = 0;
+ }
+ }
+ }
+ if (mFirst64Removed != 0) {
+ removeRemovedCallbacks(0, mFirst64Removed);
+ mFirst64Removed = 0;
+ }
+ }
+ }
+
+ /**
+ * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ */
+ private void notifyFirst64Locked(T sender, int arg, A arg2) {
+ final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
+ notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
+ }
+
+ /**
+ * Notify all callbacks using a recursive algorithm to avoid allocating on the heap.
+ * This part captures the callbacks beyond Long.SIZE that have no bits allocated for
+ * removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}.
+ * <p>
+ * Recursion is used to avoid allocating temporary state on the heap. Each stack has one
+ * long (64 callbacks) worth of information of which has been removed.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ */
+ private void notifyRecurseLocked(T sender, int arg, A arg2) {
+ final int callbackCount = mCallbacks.size();
+ final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
+
+ // Now we've got all callbacks that have no mRemainderRemoved value, so notify the
+ // others.
+ notifyRemainderLocked(sender, arg, arg2, remainderIndex);
+
+ // notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1
+ // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
+ final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
+
+ // The remaining have no bit set
+ notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
+ }
+
+ /**
+ * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If
+ * remainderIndex is -1, the first 64 will be notified instead.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param remainderIndex The index into mRemainderRemoved that should be notified.
+ */
+ private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) {
+ if (remainderIndex < 0) {
+ notifyFirst64Locked(sender, arg, arg2);
+ } else {
+ final long bits = mRemainderRemoved[remainderIndex];
+ final int startIndex = (remainderIndex + 1) * Long.SIZE;
+ final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
+ notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1);
+ notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits);
+ }
+ }
+
+ /**
+ * Notify callbacks from startIndex to endIndex, using bits as the bit status
+ * for whether they have been removed or not. bits should be from mRemainderRemoved or
+ * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to
+ * endIndex should be notified.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param startIndex The index into the mCallbacks to start notifying.
+ * @param endIndex One past the last index into mCallbacks to notify.
+ * @param bits A bit field indicating which callbacks have been removed and shouldn't
+ * be notified.
+ */
+ private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex,
+ final int endIndex, final long bits) {
+ long bitMask = 1;
+ for (int i = startIndex; i < endIndex; i++) {
+ if ((bits & bitMask) == 0) {
+ mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
+ }
+ bitMask <<= 1;
+ }
+ }
+
+ /**
+ * Add a callback to be notified. If the callback is already in the list, another won't
+ * be added. This does not affect current notifications.
+ * @param callback The callback to add.
+ */
+ public synchronized void add(C callback) {
+ int index = mCallbacks.lastIndexOf(callback);
+ if (index < 0 || isRemovedLocked(index)) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Returns true if the callback at index has been marked for removal.
+ *
+ * @param index The index into mCallbacks to check.
+ * @return true if the callback at index has been marked for removal.
+ */
+ private boolean isRemovedLocked(int index) {
+ if (index < Long.SIZE) {
+ // It is in the first 64 callbacks, just check the bit.
+ final long bitMask = 1L << index;
+ return (mFirst64Removed & bitMask) != 0;
+ } else if (mRemainderRemoved == null) {
+ // It is after the first 64 callbacks, but nothing else was marked for removal.
+ return false;
+ } else {
+ final int maskIndex = (index / Long.SIZE) - 1;
+ if (maskIndex >= mRemainderRemoved.length) {
+ // There are some items in mRemainderRemoved, but nothing at the given index.
+ return false;
+ } else {
+ // There is something marked for removal, so we have to check the bit.
+ final long bits = mRemainderRemoved[maskIndex];
+ final long bitMask = 1L << (index % Long.SIZE);
+ return (bits & bitMask) != 0;
+ }
+ }
+ }
+
+ /**
+ * Removes callbacks from startIndex to startIndex + Long.SIZE, based
+ * on the bits set in removed.
+ * @param startIndex The index into the mCallbacks to start removing callbacks.
+ * @param removed The bits indicating removal, where each bit is set for one callback
+ * to be removed.
+ */
+ private void removeRemovedCallbacks(int startIndex, long removed) {
+ // The naive approach should be fine. There may be a better bit-twiddling approach.
+ final int endIndex = startIndex + Long.SIZE;
+
+ long bitMask = 1L << (Long.SIZE - 1);
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ if ((removed & bitMask) != 0) {
+ mCallbacks.remove(i);
+ }
+ bitMask >>>= 1;
+ }
+ }
+
+ /**
+ * Remove a callback. This callback won't be notified after this call completes.
+ * @param callback The callback to remove.
+ */
+ public synchronized void remove(C callback) {
+ if (mNotificationLevel == 0) {
+ mCallbacks.remove(callback);
+ } else {
+ int index = mCallbacks.lastIndexOf(callback);
+ if (index >= 0) {
+ setRemovalBitLocked(index);
+ }
+ }
+ }
+
+ private void setRemovalBitLocked(int index) {
+ if (index < Long.SIZE) {
+ // It is in the first 64 callbacks, just check the bit.
+ final long bitMask = 1L << index;
+ mFirst64Removed |= bitMask;
+ } else {
+ final int remainderIndex = (index / Long.SIZE) - 1;
+ if (mRemainderRemoved == null) {
+ mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
+ } else if (mRemainderRemoved.length < remainderIndex) {
+ // need to make it bigger
+ long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
+ System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
+ mRemainderRemoved = newRemainders;
+ }
+ final long bitMask = 1L << (index % Long.SIZE);
+ mRemainderRemoved[remainderIndex] |= bitMask;
+ }
+ }
+
+ /**
+ * Makes a copy of the registered callbacks and returns it.
+ *
+ * @return a copy of the registered callbacks.
+ */
+ public synchronized ArrayList<C> copyListeners() {
+ ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size());
+ int numListeners = mCallbacks.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (!isRemovedLocked(i)) {
+ callbacks.add(mCallbacks.get(i));
+ }
+ }
+ return callbacks;
+ }
+
+ /**
+ * Returns true if there are no registered callbacks or false otherwise.
+ *
+ * @return true if there are no registered callbacks or false otherwise.
+ */
+ public synchronized boolean isEmpty() {
+ if (mCallbacks.isEmpty()) {
+ return true;
+ } else if (mNotificationLevel == 0) {
+ return false;
+ } else {
+ int numListeners = mCallbacks.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (!isRemovedLocked(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Removes all callbacks from the list.
+ */
+ public synchronized void clear() {
+ if (mNotificationLevel == 0) {
+ mCallbacks.clear();
+ } else if (!mCallbacks.isEmpty()) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ setRemovalBitLocked(i);
+ }
+ }
+ }
+
+ public synchronized CallbackRegistry<C, T, A> clone() {
+ CallbackRegistry<C, T, A> clone = null;
+ try {
+ clone = (CallbackRegistry<C, T, A>) super.clone();
+ clone.mFirst64Removed = 0;
+ clone.mRemainderRemoved = null;
+ clone.mNotificationLevel = 0;
+ clone.mCallbacks = new ArrayList<C>();
+ final int numListeners = mCallbacks.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (!isRemovedLocked(i)) {
+ clone.mCallbacks.add(mCallbacks.get(i));
+ }
+ }
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
+ return clone;
+ }
+
+ /**
+ * Class used to notify events from CallbackRegistry.
+ *
+ * @param <C> The callback type.
+ * @param <T> The notification sender type. Typically this is the containing class.
+ * @param <A> An opaque argument to pass to the notifier
+ */
+ public abstract static class NotifierCallback<C, T, A> {
+ /**
+ * Used to notify the callback.
+ *
+ * @param callback The callback to notify.
+ * @param sender The opaque sender object.
+ * @param arg The opaque notification parameter.
+ * @param arg2 An opaque argument passed in
+ * {@link CallbackRegistry#notifyCallbacks}
+ * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
+ */
+ public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
+ }
+}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 8ae2e3b..2785c48 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -188,18 +188,43 @@ size_t Bitmap::rowBytes() const {
return mPixelRef->rowBytes();
}
-SkPixelRef* Bitmap::pixelRef() const {
+SkPixelRef* Bitmap::peekAtPixelRef() const {
assertValid();
return mPixelRef.get();
}
+SkPixelRef* Bitmap::refPixelRef() {
+ assertValid();
+ android::AutoMutex _lock(mLock);
+ return refPixelRefLocked();
+}
+
+SkPixelRef* Bitmap::refPixelRefLocked() {
+ mPixelRef->ref();
+ if (mPixelRef->unique()) {
+ // We just restored this from 0, pin the pixels and inc the strong count
+ // Note that there *might be* an incoming onStrongRefDestroyed from whatever
+ // last unref'd
+ pinPixelsLocked();
+ mPinnedRefCount++;
+ }
+ return mPixelRef.get();
+}
+
void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes,
SkColorTable* ctable) {
+ {
+ android::AutoMutex _lock(mLock);
+ if (mPinnedRefCount) {
+ ALOGW("Called reconfigure on a bitmap that is in use! This may"
+ " cause graphical corruption!");
+ }
+ }
mPixelRef->reconfigure(info, rowBytes, ctable);
}
void Bitmap::reconfigure(const SkImageInfo& info) {
- mPixelRef->reconfigure(info, mPixelRef->rowBytes(), mPixelRef->colorTable());
+ reconfigure(info, info.minRowBytes(), nullptr);
}
void Bitmap::detachFromJava() {
@@ -287,18 +312,10 @@ void Bitmap::unpinPixelsLocked() {
void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
assertValid();
android::AutoMutex _lock(mLock);
- mPixelRef->ref();
- if (mPixelRef->unique()) {
- // We just restored this from 0, pin the pixels and inc the strong count
- // Note that there *might be* an incoming onStrongRefDestroyed from whatever
- // last unref'd
- pinPixelsLocked();
- mPinnedRefCount++;
- }
// Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes()
// would require locking the pixels first.
outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes());
- outBitmap->setPixelRef(mPixelRef.get())->unref();
+ outBitmap->setPixelRef(refPixelRefLocked())->unref();
outBitmap->setHasHardwareMipMap(hasHardwareMipMap());
}
@@ -323,7 +340,7 @@ public:
}
void* pixels() {
- return mBitmap->pixelRef()->pixels();
+ return mBitmap->peekAtPixelRef()->pixels();
}
bool valid() {
@@ -780,7 +797,7 @@ static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) {
static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
- return static_cast<jint>(bitmap->pixelRef()->getGenerationID());
+ return static_cast<jint>(bitmap->peekAtPixelRef()->getGenerationID());
}
static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) {
@@ -800,10 +817,10 @@ static void Bitmap_setHasAlpha(JNIEnv* env, jobject, jlong bitmapHandle,
jboolean hasAlpha, jboolean requestPremul) {
LocalScopedBitmap bitmap(bitmapHandle);
if (hasAlpha) {
- bitmap->pixelRef()->changeAlphaType(
+ bitmap->peekAtPixelRef()->changeAlphaType(
requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType);
} else {
- bitmap->pixelRef()->changeAlphaType(kOpaque_SkAlphaType);
+ bitmap->peekAtPixelRef()->changeAlphaType(kOpaque_SkAlphaType);
}
}
@@ -812,9 +829,9 @@ static void Bitmap_setPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle,
LocalScopedBitmap bitmap(bitmapHandle);
if (!bitmap->info().isOpaque()) {
if (isPremul) {
- bitmap->pixelRef()->changeAlphaType(kPremul_SkAlphaType);
+ bitmap->peekAtPixelRef()->changeAlphaType(kPremul_SkAlphaType);
} else {
- bitmap->pixelRef()->changeAlphaType(kUnpremul_SkAlphaType);
+ bitmap->peekAtPixelRef()->changeAlphaType(kUnpremul_SkAlphaType);
}
}
}
@@ -1164,7 +1181,7 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle,
static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
- SkPixelRef* pixelRef = bitmap.valid() ? bitmap->pixelRef() : nullptr;
+ SkPixelRef* pixelRef = bitmap.valid() ? bitmap->peekAtPixelRef() : nullptr;
SkSafeRef(pixelRef);
return reinterpret_cast<jlong>(pixelRef);
}
diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h
index d6e5c61..efeb898 100644
--- a/core/jni/android/graphics/Bitmap.h
+++ b/core/jni/android/graphics/Bitmap.h
@@ -62,7 +62,8 @@ public:
int width() const { return info().width(); }
int height() const { return info().height(); }
size_t rowBytes() const;
- SkPixelRef* pixelRef() const;
+ SkPixelRef* peekAtPixelRef() const;
+ SkPixelRef* refPixelRef();
bool valid() const { return mPixelStorageType != PixelStorageType::Invalid; }
void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
@@ -88,6 +89,7 @@ private:
JNIEnv* jniEnv();
bool shouldDisposeSelfLocked();
void assertValid() const;
+ SkPixelRef* refPixelRefLocked();
android::Mutex mLock;
int mPinnedRefCount = 0;
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index cdd397d..3ca4e72 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -184,7 +184,7 @@ public:
}
mBitmap->reconfigure(info, bitmap->rowBytes(), ctable);
- bitmap->setPixelRef(mBitmap->pixelRef());
+ bitmap->setPixelRef(mBitmap->refPixelRef())->unref();
// since we're already allocated, we lockPixels right away
// HeapAllocator/JavaPixelAllocator behaves this way too
@@ -258,7 +258,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
unsigned int existingBufferSize = 0;
if (javaBitmap != NULL) {
reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
- if (reuseBitmap->pixelRef()->isImmutable()) {
+ if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
javaBitmap = NULL;
reuseBitmap = nullptr;
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 0deb8cc..1c6f7de 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -352,8 +352,8 @@ void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap)
getBitmap(env, bitmap)->getSkBitmap(outBitmap);
}
-SkPixelRef* GraphicsJNI::getSkPixelRef(JNIEnv* env, jobject bitmap) {
- return getBitmap(env, bitmap)->pixelRef();
+SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) {
+ return getBitmap(env, bitmap)->refPixelRef();
}
SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) {
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index e748bac..ef9c2a9 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -52,7 +52,7 @@ public:
static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas);
static android::Bitmap* getBitmap(JNIEnv*, jobject bitmap);
static void getSkBitmap(JNIEnv*, jobject bitmap, SkBitmap* outBitmap);
- static SkPixelRef* getSkPixelRef(JNIEnv*, jobject bitmap);
+ static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap);
static SkRegion* getNativeRegion(JNIEnv*, jobject region);
// Given the 'native' long held by the Rasterizer.java object, return a
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index db495dd..74a9e4e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -976,6 +976,12 @@ static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz,
dest->setTo(*src);
}
+static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle)
+{
+ ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
+ theme->clear();
+}
+
static jint android_content_AssetManager_loadThemeAttributeValue(
JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve)
{
@@ -2108,6 +2114,8 @@ static JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_applyThemeStyle },
{ "copyTheme", "(JJ)V",
(void*) android_content_AssetManager_copyTheme },
+ { "clearTheme", "(J)V",
+ (void*) android_content_AssetManager_clearTheme },
{ "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadThemeAttributeValue },
{ "getThemeChangingConfigurations", "(J)I",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1965cd3..77af341 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -180,7 +180,7 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
(void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot,
screenshotInfo, rowBytes, nullptr);
screenshot.detach();
- bitmap->pixelRef()->setImmutable();
+ bitmap->peekAtPixelRef()->setImmutable();
return GraphicsJNI::createBitmap(env, bitmap,
GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
diff --git a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png
deleted file mode 100644
index 0179433..0000000
--- a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png
deleted file mode 100644
index e5760be..0000000
--- a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png
deleted file mode 100644
index 3939214..0000000
--- a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png
deleted file mode 100644
index 432c385..0000000
--- a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/text_cursor_material.xml b/core/res/res/drawable/text_cursor_material.xml
index a350c47..0bedaa9 100644
--- a/core/res/res/drawable/text_cursor_material.xml
+++ b/core/res/res/drawable/text_cursor_material.xml
@@ -14,6 +14,15 @@
limitations under the License.
-->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/text_cursor_mtrl_alpha"
- android:tint="?attr/colorControlActivated" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:inset="2dp">
+ <shape
+ android:tint="?attr/colorControlActivated"
+ android:shape="rectangle">
+ <size
+ android:height="2dp"
+ android:width="2dp" />
+ <solid
+ android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index 89c3749..4b544d2 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -16,7 +16,6 @@
-->
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -27,8 +26,7 @@
android:layout_column="0"
android:layout_row="0"
android:layout_rowSpan="3"
- android:layout_gravity="center|fill"
- tools:background="@color/accent_material_light" />
+ android:layout_gravity="center|fill" />
<RelativeLayout
android:layout_width="wrap_content"
@@ -56,20 +54,14 @@
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="right"
- tools:text="23"
- tools:textSize="@dimen/timepicker_time_label_size"
- tools:textColor="@color/white" />
+ android:gravity="right" />
<TextView
android:id="@+id/separator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
- android:importantForAccessibility="no"
- tools:text=":"
- tools:textSize="@dimen/timepicker_time_label_size"
- tools:textColor="@color/white" />
+ android:importantForAccessibility="no" />
<!-- The minutes should always be to the right of the separator,
regardless of the current locale's layout direction. -->
@@ -80,10 +72,7 @@
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="left"
- tools:text="59"
- tools:textSize="@dimen/timepicker_time_label_size"
- tools:textColor="@color/white" />
+ android:gravity="left" />
</LinearLayout>
<!-- The layout alignment of this view will switch between toRightOf
@@ -106,10 +95,7 @@
android:paddingTop="@dimen/timepicker_am_top_padding"
android:lines="1"
android:ellipsize="none"
- android:includeFontPadding="false"
- tools:text="AM"
- tools:textSize="@dimen/timepicker_ampm_label_size"
- tools:textColor="@color/white" />
+ android:includeFontPadding="false" />
<CheckedTextView
android:id="@+id/pm_label"
@@ -121,10 +107,7 @@
android:paddingTop="@dimen/timepicker_pm_top_padding"
android:lines="1"
android:ellipsize="none"
- android:includeFontPadding="false"
- tools:text="PM"
- tools:textSize="@dimen/timepicker_ampm_label_size"
- tools:textColor="@color/white" />
+ android:includeFontPadding="false" />
</LinearLayout>
</RelativeLayout>
diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml
index 2150341..a4388f6 100644
--- a/core/res/res/layout/date_picker_header_material.xml
+++ b/core/res/res/layout/date_picker_header_material.xml
@@ -16,17 +16,13 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/date_picker_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="18dp"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding"
- android:orientation="vertical"
- tools:background="@color/accent_material_light"
- tools:paddingStart="24dp"
- tools:paddingEnd="24dp">
+ android:orientation="vertical">
<!-- Top padding should stay on this view so that
the touch target is a bit larger. -->
@@ -35,10 +31,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
- android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel"
- tools:text="2015"
- tools:textSize="@dimen/date_picker_year_label_size"
- tools:textColor="@color/white" />
+ android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" />
<TextView
android:id="@+id/date_picker_header_date"
@@ -47,9 +40,6 @@
android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel"
android:gravity="start"
android:maxLines="2"
- android:ellipsize="none"
- tools:text="Thu, Sep 30"
- tools:textSize="@dimen/date_picker_date_label_size"
- tools:textColor="@color/white" />
+ android:ellipsize="none" />
</LinearLayout>
diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml
index be9e443..3f5e300 100644
--- a/core/res/res/layout/time_picker_header_material.xml
+++ b/core/res/res/layout/time_picker_header_material.xml
@@ -18,13 +18,11 @@
<!-- This layout is duplicated in land/time_picker_material.xml, so any
changes made here need to be manually copied over. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/time_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:padding="@dimen/timepicker_separator_padding"
- tools:background="@color/accent_material_light">
+ android:padding="@dimen/timepicker_separator_padding">
<!-- The hour should always be to the left of the separator,
regardless of the current locale's layout direction. -->
@@ -37,10 +35,7 @@
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="right"
- tools:text="23"
- tools:textSize="@dimen/timepicker_time_label_size"
- tools:textColor="@color/white" />
+ android:gravity="right" />
<TextView
android:id="@+id/separator"
@@ -50,10 +45,7 @@
android:layout_marginRight="@dimen/timepicker_separator_padding"
android:layout_centerInParent="true"
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
- android:importantForAccessibility="no"
- tools:text=":"
- tools:textSize="@dimen/timepicker_time_label_size"
- tools:textColor="@color/white" />
+ android:importantForAccessibility="no" />
<!-- The minutes should always be to the left of the separator,
regardless of the current locale's layout direction. -->
@@ -66,10 +58,7 @@
android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
android:singleLine="true"
android:ellipsize="none"
- android:gravity="left"
- tools:text="59"
- tools:textSize="@dimen/timepicker_time_label_size"
- tools:textColor="@color/white" />
+ android:gravity="left" />
<!-- The layout alignment of this view will switch between toRightOf
@id/minutes and toLeftOf @id/hours depending on the locale. -->
@@ -90,10 +79,7 @@
android:paddingTop="@dimen/timepicker_am_top_padding"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
android:lines="1"
- android:ellipsize="none"
- tools:text="AM"
- tools:textSize="@dimen/timepicker_ampm_label_size"
- tools:textColor="@color/white" />
+ android:ellipsize="none" />
<CheckedTextView
android:id="@+id/pm_label"
android:layout_width="wrap_content"
@@ -103,9 +89,6 @@
android:paddingTop="@dimen/timepicker_pm_top_padding"
android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
android:lines="1"
- android:ellipsize="none"
- tools:text="PM"
- tools:textSize="@dimen/timepicker_ampm_label_size"
- tools:textColor="@color/white" />
+ android:ellipsize="none" />
</LinearLayout>
</RelativeLayout>
diff --git a/core/res/res/transition/popup_window_enter.xml b/core/res/res/transition/popup_window_enter.xml
index 38c41f0..c4c8dac 100644
--- a/core/res/res/transition/popup_window_enter.xml
+++ b/core/res/res/transition/popup_window_enter.xml
@@ -16,17 +16,14 @@
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
- <!-- Start from location of epicenter, move to popup location. -->
- <transition
- class="com.android.internal.transition.EpicenterTranslate"
- android:duration="300" />
-
<!-- Start from size of epicenter, expand to full width/height. -->
<transition
- class="com.android.internal.transition.EpicenterClipReveal"
- android:centerClipBounds="true"
- android:duration="300" />
+ class="com.android.internal.transition.EpicenterTranslateClipReveal"
+ android:duration="250" />
<!-- Quickly fade in. -->
- <fade android:duration="100" />
+ <fade
+ android:duration="100"
+ android:fromAlpha="0.1"
+ android:toAlpha="1.0" />
</transitionSet>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1c4b5f7..eaa6278 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5877,16 +5877,9 @@
</declare-styleable>
<!-- @hide For internal use only. Use only as directed. -->
- <declare-styleable name="EpicenterClipReveal">
- <attr name="centerClipBounds" format="boolean" />
+ <declare-styleable name="EpicenterTranslateClipReveal">
<attr name="interpolatorX" format="reference" />
<attr name="interpolatorY" format="reference" />
- </declare-styleable>
-
- <!-- @hide For internal use only. Use only as directed. -->
- <declare-styleable name="EpicenterTranslate">
- <attr name="interpolatorX" />
- <attr name="interpolatorY" />
<attr name="interpolatorZ" format="reference" />
</declare-styleable>
diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java
deleted file mode 100644
index f479e2b..0000000
--- a/core/tests/coretests/src/android/util/FloatMathTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2007 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 junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-public class FloatMathTest extends TestCase {
-
- @SmallTest
- public void testSqrt() {
- assertEquals(7, FloatMath.sqrt(49), 0);
- assertEquals(10, FloatMath.sqrt(100), 0);
- assertEquals(0, FloatMath.sqrt(0), 0);
- assertEquals(1, FloatMath.sqrt(1), 0);
- }
-
- @SmallTest
- public void testFloor() {
- assertEquals(78, FloatMath.floor(78.89f), 0);
- assertEquals(-79, FloatMath.floor(-78.89f), 0);
- }
-
- @SmallTest
- public void testCeil() {
- assertEquals(79, FloatMath.ceil(78.89f), 0);
- assertEquals(-78, FloatMath.ceil(-78.89f), 0);
- }
-
- @SmallTest
- public void testSin() {
- assertEquals(0.0, FloatMath.sin(0), 0);
- assertEquals(0.8414709848078965f, FloatMath.sin(1), 0);
- }
-
- @SmallTest
- public void testCos() {
- assertEquals(1.0f, FloatMath.cos(0), 0);
- assertEquals(0.5403023058681398f, FloatMath.cos(1), 0);
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java
new file mode 100644
index 0000000..c53f4cc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class CallbackRegistryTest extends TestCase {
+
+ final Integer callback1 = 1;
+ final Integer callback2 = 2;
+ final Integer callback3 = 3;
+ CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry;
+ int notify1;
+ int notify2;
+ int notify3;
+ int[] deepNotifyCount = new int[300];
+ Integer argValue;
+
+ private void addNotifyCount(Integer callback) {
+ if (callback == callback1) {
+ notify1++;
+ } else if (callback == callback2) {
+ notify2++;
+ } else if (callback == callback3) {
+ notify3++;
+ }
+ deepNotifyCount[callback]++;
+ }
+
+ public void testAddListener() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg, Integer arg2) {
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ Integer callback = 0;
+
+ assertNotNull(registry.copyListeners());
+ assertEquals(0, registry.copyListeners().size());
+
+ registry.add(callback);
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(1, callbacks.size());
+ assertEquals(callback, callbacks.get(0));
+
+ registry.add(callback);
+ callbacks = registry.copyListeners();
+ assertEquals(1, callbacks.size());
+ assertEquals(callback, callbacks.get(0));
+
+ Integer otherListener = 1;
+ registry.add(otherListener);
+ callbacks = registry.copyListeners();
+ assertEquals(2, callbacks.size());
+ assertEquals(callback, callbacks.get(0));
+ assertEquals(otherListener, callbacks.get(1));
+
+ registry.remove(callback);
+ registry.add(callback);
+ callbacks = registry.copyListeners();
+ assertEquals(2, callbacks.size());
+ assertEquals(callback, callbacks.get(1));
+ assertEquals(otherListener, callbacks.get(0));
+ }
+
+ public void testSimpleNotify() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ assertEquals(arg1, (int) arg);
+ addNotifyCount(callback);
+ argValue = arg;
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ registry.add(callback2);
+ Integer arg = 1;
+ registry.notifyCallbacks(this, arg, arg);
+ assertEquals(arg, argValue);
+ assertEquals(1, notify2);
+ }
+
+ public void testRemoveWhileNotifying() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ if (callback == callback1) {
+ registry.remove(callback1);
+ registry.remove(callback2);
+ }
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ registry.add(callback1);
+ registry.add(callback2);
+ registry.add(callback3);
+ registry.notifyCallbacks(this, 0, null);
+ assertEquals(1, notify1);
+ assertEquals(1, notify2);
+ assertEquals(1, notify3);
+
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(1, callbacks.size());
+ assertEquals(callback3, callbacks.get(0));
+ }
+
+ public void testDeepRemoveWhileNotifying() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ registry.remove(callback);
+ registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null);
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ registry.add(callback1);
+ registry.add(callback2);
+ registry.add(callback3);
+ registry.notifyCallbacks(this, 0, null);
+ assertEquals(1, notify1);
+ assertEquals(2, notify2);
+ assertEquals(3, notify3);
+
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(0, callbacks.size());
+ }
+
+ public void testAddRemovedListener() {
+
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ if (callback == callback1) {
+ registry.remove(callback2);
+ } else if (callback == callback3) {
+ registry.add(callback2);
+ }
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+
+ registry.add(callback1);
+ registry.add(callback2);
+ registry.add(callback3);
+ registry.notifyCallbacks(this, 0, null);
+
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(3, callbacks.size());
+ assertEquals(callback1, callbacks.get(0));
+ assertEquals(callback3, callbacks.get(1));
+ assertEquals(callback2, callbacks.get(2));
+ assertEquals(1, notify1);
+ assertEquals(1, notify2);
+ assertEquals(1, notify3);
+ }
+
+ public void testVeryDeepRemoveWhileNotifying() {
+ final Integer[] callbacks = new Integer[deepNotifyCount.length];
+ for (int i = 0; i < callbacks.length; i++) {
+ callbacks[i] = i;
+ }
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ registry.remove(callback);
+ registry.remove(callbacks[callbacks.length - callback - 1]);
+ registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null);
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ for (int i = 0; i < callbacks.length; i++) {
+ registry.add(callbacks[i]);
+ }
+ registry.notifyCallbacks(this, 0, null);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ int expectedCount = Math.min(i + 1, deepNotifyCount.length - i);
+ assertEquals(expectedCount, deepNotifyCount[i]);
+ }
+
+ ArrayList<Integer> callbackList = registry.copyListeners();
+ assertEquals(0, callbackList.size());
+ }
+
+ public void testClear() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ registry.add(i);
+ }
+ registry.clear();
+
+ ArrayList<Integer> callbackList = registry.copyListeners();
+ assertEquals(0, callbackList.size());
+
+ registry.notifyCallbacks(this, 0, null);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ assertEquals(0, deepNotifyCount[i]);
+ }
+ }
+
+ public void testNestedClear() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ registry.clear();
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ registry.add(i);
+ }
+ registry.notifyCallbacks(this, 0, null);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ assertEquals(1, deepNotifyCount[i]);
+ }
+
+ ArrayList<Integer> callbackList = registry.copyListeners();
+ assertEquals(0, callbackList.size());
+ }
+
+ public void testIsEmpty() throws Exception {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg, Integer arg2) {
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ Integer callback = 0;
+
+ assertTrue(registry.isEmpty());
+ registry.add(callback);
+ assertFalse(registry.isEmpty());
+ }
+
+ public void testClone() throws Exception {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg, Integer arg2) {
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+
+ assertTrue(registry.isEmpty());
+ CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone();
+ Integer callback = 0;
+ registry.add(callback);
+ assertFalse(registry.isEmpty());
+ assertTrue(registry2.isEmpty());
+ registry2 = registry.clone();
+ assertFalse(registry2.isEmpty());
+ }
+}