diff options
Diffstat (limited to 'core')
106 files changed, 2486 insertions, 726 deletions
diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java index e738da1..2640457 100644 --- a/core/java/android/animation/TypeEvaluator.java +++ b/core/java/android/animation/TypeEvaluator.java @@ -19,7 +19,7 @@ package android.animation; /** * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators * allow developers to create animations on arbitrary property types, by allowing them to supply - * custom evaulators for types that are not automatically understood and used by the animation + * custom evaluators for types that are not automatically understood and used by the animation * system. * * @see ValueAnimator#setEvaluator(TypeEvaluator) @@ -41,4 +41,4 @@ public interface TypeEvaluator<T> { */ public T evaluate(float fraction, T startValue, T endValue); -}
\ No newline at end of file +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2a28b76..e6960b3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3865,7 +3865,7 @@ public final class ActivityThread { } } - final void freeTextLayoutCachesIfNeeded(int configDiff) { + static void freeTextLayoutCachesIfNeeded(int configDiff) { if (configDiff != 0) { // Ask text layout engine to free its caches if there is a locale change boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 7358f73..a7789d6 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1328,12 +1328,19 @@ final class FragmentManagerImpl extends FragmentManager { } } + /** + * Adds an action to the queue of pending actions. + * + * @param action the action to add + * @param allowStateLoss whether to allow loss of state information + * @throws IllegalStateException if the activity has been destroyed + */ public void enqueueAction(Runnable action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { - if (mActivity == null) { + if (mDestroyed || mActivity == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index b84889f..4ca3747 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -175,16 +175,20 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null; mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(), - getFilesDir().getAbsolutePath(), getObbDir().getAbsolutePath(), - getExternalFilesDir(null).getAbsolutePath(), - Build.VERSION.SDK_INT, getAssets(), nativeSavedState); - + getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()), + getAbsolutePath(getExternalFilesDir(null)), + Build.VERSION.SDK_INT, getAssets(), nativeSavedState); + if (mNativeHandle == 0) { throw new IllegalArgumentException("Unable to load native library: " + path); } super.onCreate(savedInstanceState); } + private static String getAbsolutePath(File file) { + return (file != null) ? file.getAbsolutePath() : null; + } + @Override protected void onDestroy() { mDestroyed = true; diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index d7d8cdb..e7e4a0f 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -388,6 +388,66 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** + * Checks if Avrcp device supports the absolute volume feature. + * + * @return true if device supports absolute volume + * @hide + */ + public boolean isAvrcpAbsoluteVolumeSupported() { + if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); + if (mService != null && isEnabled()) { + try { + return mService.isAvrcpAbsoluteVolumeSupported(); + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Tells remote device to adjust volume. Only if absolute volume is supported. + * + * @param direction 1 to increase volume, or -1 to decrease volume + * @hide + */ + public void adjustAvrcpAbsoluteVolume(int direction) { + if (DBG) Log.d(TAG, "adjustAvrcpAbsoluteVolume"); + if (mService != null && isEnabled()) { + try { + mService.adjustAvrcpAbsoluteVolume(direction); + return; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in adjustAvrcpAbsoluteVolume()", e); + return; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + } + + /** + * Tells remote device to set an absolute volume. Only if absolute volume is supported + * + * @param volume Absolute volume to be set on AVRCP side + * @hide + */ + public void setAvrcpAbsoluteVolume(int volume) { + if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); + if (mService != null && isEnabled()) { + try { + mService.setAvrcpAbsoluteVolume(volume); + return; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e); + return; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + } + + /** * Check if A2DP profile is streaming music. * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index fe66fbd..6609b98 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -56,6 +56,8 @@ public final class BluetoothUuid { ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid Hid = ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); + public static final ParcelUuid Hogp = + ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid PANU = ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid NAP = diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 1f10998..26ff9e2 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -32,5 +32,8 @@ interface IBluetoothA2dp { int getConnectionState(in BluetoothDevice device); boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); + boolean isAvrcpAbsoluteVolumeSupported(); + oneway void adjustAvrcpAbsoluteVolume(int direction); + oneway void setAvrcpAbsoluteVolume(int volume); boolean isA2dpPlaying(in BluetoothDevice device); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 45fed2d..5cabfee 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2060,7 +2060,7 @@ public abstract class ContentResolver { private final class ParcelFileDescriptorInner extends ParcelFileDescriptor { private final IContentProvider mContentProvider; - private boolean mReleaseProviderFlag = false; + private boolean mProviderReleased; ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) { super(pfd); @@ -2069,17 +2069,10 @@ public abstract class ContentResolver { @Override public void close() throws IOException { - if(!mReleaseProviderFlag) { - super.close(); + super.close(); + if (!mProviderReleased) { ContentResolver.this.releaseProvider(mContentProvider); - mReleaseProviderFlag = true; - } - } - - @Override - protected void finalize() throws Throwable { - if (!mReleaseProviderFlag) { - close(); + mProviderReleased = true; } } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ff350b9..017ad98 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2678,6 +2678,10 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; + /** {@hide} */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_DOCUMENT = "android.intent.action.MANAGE_DOCUMENT"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java index 6e68b6b..2a0d9b9 100644 --- a/core/java/android/database/MatrixCursor.java +++ b/core/java/android/database/MatrixCursor.java @@ -83,11 +83,10 @@ public class MatrixCursor extends AbstractCursor { * row */ public RowBuilder newRow() { - rowCount++; - int endIndex = rowCount * columnCount; + final int row = rowCount++; + final int endIndex = rowCount * columnCount; ensureCapacity(endIndex); - int start = endIndex - columnCount; - return new RowBuilder(start, endIndex); + return new RowBuilder(row); } /** @@ -180,18 +179,29 @@ public class MatrixCursor extends AbstractCursor { } /** - * Builds a row, starting from the left-most column and adding one column - * value at a time. Follows the same ordering as the column names specified - * at cursor construction time. + * Builds a row of values using either of these approaches: + * <ul> + * <li>Values can be added with explicit column ordering using + * {@link #add(Object)}, which starts from the left-most column and adds one + * column value at a time. This follows the same ordering as the column + * names specified at cursor construction time. + * <li>Column and value pairs can be offered for possible inclusion using + * {@link #offer(String, Object)}. If the cursor includes the given column, + * the value will be set for that column, otherwise the value is ignored. + * This approach is useful when matching data to a custom projection. + * </ul> + * Undefined values are left as {@code null}. */ public class RowBuilder { + private final int row; + private final int endIndex; private int index; - private final int endIndex; - RowBuilder(int index, int endIndex) { - this.index = index; - this.endIndex = endIndex; + RowBuilder(int row) { + this.row = row; + this.index = row * columnCount; + this.endIndex = index + columnCount; } /** @@ -210,6 +220,21 @@ public class MatrixCursor extends AbstractCursor { data[index++] = columnValue; return this; } + + /** + * Offer value for possible inclusion if this cursor defines the given + * column. Columns not defined by the cursor are silently ignored. + * + * @return this builder to support chaining + */ + public RowBuilder offer(String columnName, Object value) { + for (int i = 0; i < columnNames.length; i++) { + if (columnName.equals(columnNames[i])) { + data[(row * columnCount) + i] = value; + } + } + return this; + } } // AbstractCursor implementation. diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java index b6c260f..2d47f28 100644 --- a/core/java/android/gesture/GestureOverlayView.java +++ b/core/java/android/gesture/GestureOverlayView.java @@ -486,6 +486,7 @@ public class GestureOverlayView extends FrameLayout { @Override protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); cancelClearAnimation(); } diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl index 81e564e..fc54828 100644 --- a/core/java/android/hardware/ICameraService.aidl +++ b/core/java/android/hardware/ICameraService.aidl @@ -22,6 +22,7 @@ import android.hardware.IProCameraUser; import android.hardware.IProCameraCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.utils.BinderHolder; import android.hardware.ICameraServiceListener; import android.hardware.CameraInfo; @@ -37,17 +38,23 @@ interface ICameraService int getCameraInfo(int cameraId, out CameraInfo info); - ICamera connect(ICameraClient client, int cameraId, + int connect(ICameraClient client, int cameraId, String clientPackageName, - int clientUid); + int clientUid, + // Container for an ICamera object + out BinderHolder device); - IProCameraUser connectPro(IProCameraCallbacks callbacks, int cameraId, + int connectPro(IProCameraCallbacks callbacks, int cameraId, String clientPackageName, - int clientUid); + int clientUid, + // Container for an IProCameraUser object + out BinderHolder device); - ICameraDeviceUser connectDevice(ICameraDeviceCallbacks callbacks, int cameraId, + int connectDevice(ICameraDeviceCallbacks callbacks, int cameraId, String clientPackageName, - int clientUid); + int clientUid, + // Container for an ICameraDeviceUser object + out BinderHolder device); int addListener(ICameraServiceListener listener); int removeListener(ICameraServiceListener listener); diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java index 0089f26..e08d1e6 100644 --- a/core/java/android/hardware/camera2/CameraAccessException.java +++ b/core/java/android/hardware/camera2/CameraAccessException.java @@ -48,11 +48,18 @@ public class CameraAccessException extends AndroidException { /** * The camera device is removable and has been disconnected from the Android - * device, or the camera service has shut down the connection due to a + * device, or the camera id used with {@link android.hardware.camera2.CameraManager#openCamera} + * is no longer valid, or the camera service has shut down the connection due to a * higher-priority access request for the camera device. */ public static final int CAMERA_DISCONNECTED = 4; + /** + * A deprecated HAL version is in use. + * @hide + */ + public static final int CAMERA_DEPRECATED_HAL = 1000; + // Make the eclipse warning about serializable exceptions go away private static final long serialVersionUID = 5630338637471475675L; // randomly generated diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 1370654..b8ec4da 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -22,6 +22,7 @@ import android.hardware.ICameraServiceListener; import android.hardware.IProCameraUser; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; +import android.hardware.camera2.utils.BinderHolder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -171,10 +172,10 @@ public final class CameraManager { * * @param cameraId The unique identifier of the camera device to open * - * @throws IllegalArgumentException if the cameraId does not match any - * currently connected camera device. * @throws CameraAccessException if the camera is disabled by device policy, - * or too many camera devices are already open. + * or too many camera devices are already open, or the cameraId does not match + * any currently available camera device. + * * @throws SecurityException if the application does not have permission to * access the camera * @@ -192,16 +193,11 @@ public final class CameraManager { android.hardware.camera2.impl.CameraDevice device = new android.hardware.camera2.impl.CameraDevice(cameraId); - cameraUser = mCameraService.connectDevice(device.getCallbacks(), + BinderHolder holder = new BinderHolder(); + mCameraService.connectDevice(device.getCallbacks(), Integer.parseInt(cameraId), - mContext.getPackageName(), USE_CALLING_UID); - - // TODO: change ICameraService#connectDevice to return status_t - if (cameraUser == null) { - // TEMPORARY CODE. - // catch-all exception since we aren't yet getting the actual error code - throw new IllegalStateException("Failed to open camera device"); - } + mContext.getPackageName(), USE_CALLING_UID, holder); + cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); // TODO: factor out listener to be non-nested, then move setter to constructor device.setRemoteDevice(cameraUser); @@ -214,12 +210,7 @@ public final class CameraManager { throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " + cameraId); } catch (CameraRuntimeException e) { - if (e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { - throw new IllegalArgumentException("Invalid camera ID specified -- " + - "perhaps the camera was physically disconnected", e); - } else { - throw e.asChecked(); - } + throw e.asChecked(); } catch (RemoteException e) { // impossible return null; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 1a10bdd..63d6134 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -53,6 +53,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { private final Object mLock = new Object(); private final HashSet<Surface> mSurfaceSet = new HashSet<Surface>(); + private Object mUserTag; /** * @hide @@ -89,6 +90,42 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { } } + /** + * Set a tag for this request. + * + * <p>This tag is not used for anything by the camera device, but can be + * used by an application to easily identify a CaptureRequest when it is + * returned by + * {@link CameraDevice.CaptureListener#onCaptureComplete CaptureListener.onCaptureComplete} + * + * @param tag an arbitrary Object to store with this request + * @see #getTag + */ + public void setTag(Object tag) { + synchronized (mLock) { + mUserTag = tag; + } + } + + /** + * Retrieve the tag for this request, if any. + * + * <p>This tag is not used for anything by the camera device, but can be + * used by an application to easily identify a CaptureRequest when it is + * returned by + * {@link CameraDevice.CaptureListener#onCaptureComplete CaptureListener.onCaptureComplete} + * </p> + * + * @return the last tag Object set on this request, or {@code null} if + * no tag has been set. + * @see #setTag + */ + public Object getTag() { + synchronized (mLock) { + return mUserTag; + } + } + public static final Parcelable.Creator<CaptureRequest> CREATOR = new Parcelable.Creator<CaptureRequest>() { @Override diff --git a/core/java/android/hardware/camera2/utils/BinderHolder.aidl b/core/java/android/hardware/camera2/utils/BinderHolder.aidl new file mode 100644 index 0000000..f39d645 --- /dev/null +++ b/core/java/android/hardware/camera2/utils/BinderHolder.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.utils; + +/** @hide */ +parcelable BinderHolder; diff --git a/core/java/android/hardware/camera2/utils/BinderHolder.java b/core/java/android/hardware/camera2/utils/BinderHolder.java new file mode 100644 index 0000000..9eea390 --- /dev/null +++ b/core/java/android/hardware/camera2/utils/BinderHolder.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.utils; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.IBinder; + +/** + * @hide + */ +public class BinderHolder implements Parcelable { + private IBinder mBinder = null; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mBinder); + } + + public void readFromParcel(Parcel src) { + mBinder = src.readStrongBinder(); + } + + public static final Parcelable.Creator<BinderHolder> CREATOR = + new Parcelable.Creator<BinderHolder>() { + @Override + public BinderHolder createFromParcel(Parcel in) { + return new BinderHolder(in); + } + + @Override + public BinderHolder[] newArray(int size) { + return new BinderHolder[size]; + } + }; + + public IBinder getBinder() { + return mBinder; + } + + public void setBinder(IBinder binder) { + mBinder = binder; + } + + public BinderHolder() {} + + public BinderHolder(IBinder binder) { + mBinder = binder; + } + + private BinderHolder(Parcel in) { + mBinder = in.readStrongBinder(); + } +} + diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java index 586c759..fbe7ff4 100644 --- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java +++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java @@ -19,6 +19,7 @@ package android.hardware.camera2.utils; import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED; import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED; import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; +import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL; import android.os.DeadObjectException; import android.os.RemoteException; @@ -48,6 +49,7 @@ public class CameraBinderDecorator { public static final int EACCES = -13; public static final int EBUSY = -16; public static final int ENODEV = -19; + public static final int ENOTSUP = -129; private static class CameraBinderDecoratorListener implements Decorator.DecoratorListener { @@ -75,9 +77,6 @@ public class CameraBinderDecorator { case DEAD_OBJECT: UncheckedThrow.throwAnyException(new CameraRuntimeException( CAMERA_DISCONNECTED)); - // TODO: Camera service (native side) should return - // EACCES error - // when there's a policy manager disabled causing this case EACCES: UncheckedThrow.throwAnyException(new CameraRuntimeException( CAMERA_DISABLED)); @@ -87,6 +86,9 @@ public class CameraBinderDecorator { case ENODEV: UncheckedThrow.throwAnyException(new CameraRuntimeException( CAMERA_DISCONNECTED)); + case ENOTSUP: + UncheckedThrow.throwAnyException(new CameraRuntimeException( + CAMERA_DEPRECATED_HAL)); } /** diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 12f7915..46b0150 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1520,6 +1520,11 @@ public final class Parcel { return fd != null ? new ParcelFileDescriptor(fd) : null; } + /** {@hide} */ + public final FileDescriptor readRawFileDescriptor() { + return nativeReadFileDescriptor(mNativePtr); + } + /*package*/ static native FileDescriptor openFileDescriptor(String file, int mode) throws FileNotFoundException; /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig) diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 3de362c..579971d 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -16,8 +16,25 @@ package android.os; +import static libcore.io.OsConstants.AF_UNIX; +import static libcore.io.OsConstants.SEEK_SET; +import static libcore.io.OsConstants.SOCK_STREAM; +import static libcore.io.OsConstants.S_ISLNK; +import static libcore.io.OsConstants.S_ISREG; + +import android.content.BroadcastReceiver; +import android.content.ContentProvider; +import android.util.Log; + import dalvik.system.CloseGuard; +import libcore.io.ErrnoException; +import libcore.io.IoUtils; +import libcore.io.Libcore; +import libcore.io.Memory; +import libcore.io.OsConstants; +import libcore.io.StructStat; + import java.io.Closeable; import java.io.File; import java.io.FileDescriptor; @@ -27,36 +44,80 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.DatagramSocket; import java.net.Socket; +import java.nio.ByteOrder; /** * The FileDescriptor returned by {@link Parcel#readFileDescriptor}, allowing * you to close it when done with it. */ public class ParcelFileDescriptor implements Parcelable, Closeable { - private final FileDescriptor mFileDescriptor; + private static final String TAG = "ParcelFileDescriptor"; + + private final FileDescriptor mFd; + + /** + * Optional socket used to communicate close events, status at close, and + * detect remote process crashes. + */ + private FileDescriptor mCommFd; /** * Wrapped {@link ParcelFileDescriptor}, if any. Used to avoid - * double-closing {@link #mFileDescriptor}. + * double-closing {@link #mFd}. */ private final ParcelFileDescriptor mWrapped; + /** + * Maximum {@link #mStatusBuf} size; longer status messages will be + * truncated. + */ + private static final int MAX_STATUS = 1024; + + /** + * Temporary buffer used by {@link #readCommStatus(FileDescriptor, byte[])}, + * allocated on-demand. + */ + private byte[] mStatusBuf; + + /** + * Status read by {@link #checkError(boolean)}, or null if not read yet. + */ + private Status mStatus; + private volatile boolean mClosed; private final CloseGuard mGuard = CloseGuard.get(); /** - * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied - * and this file doesn't already exist, then create the file with - * permissions such that any application can read it. + * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and + * this file doesn't already exist, then create the file with permissions + * such that any application can read it. + * + * @deprecated Creating world-readable files is very dangerous, and likely + * to cause security holes in applications. It is strongly + * discouraged; instead, applications should use more formal + * mechanism for interactions such as {@link ContentProvider}, + * {@link BroadcastReceiver}, and {@link android.app.Service}. + * There are no guarantees that this access mode will remain on + * a file, such as when it goes through a backup and restore. */ + @Deprecated public static final int MODE_WORLD_READABLE = 0x00000001; /** - * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied - * and this file doesn't already exist, then create the file with - * permissions such that any application can write it. + * For use with {@link #open}: if {@link #MODE_CREATE} has been supplied and + * this file doesn't already exist, then create the file with permissions + * such that any application can write it. + * + * @deprecated Creating world-writable files is very dangerous, and likely + * to cause security holes in applications. It is strongly + * discouraged; instead, applications should use more formal + * mechanism for interactions such as {@link ContentProvider}, + * {@link BroadcastReceiver}, and {@link android.app.Service}. + * There are no guarantees that this access mode will remain on + * a file, such as when it goes through a backup and restore. */ + @Deprecated public static final int MODE_WORLD_WRITEABLE = 0x00000002; /** @@ -90,32 +151,102 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { public static final int MODE_APPEND = 0x02000000; /** + * Create a new ParcelFileDescriptor wrapped around another descriptor. By + * default all method calls are delegated to the wrapped descriptor. + */ + public ParcelFileDescriptor(ParcelFileDescriptor wrapped) { + // We keep a strong reference to the wrapped PFD, and rely on its + // finalizer to trigger CloseGuard. All calls are delegated to wrapper. + mWrapped = wrapped; + mFd = null; + mCommFd = null; + mClosed = true; + } + + /** {@hide} */ + public ParcelFileDescriptor(FileDescriptor fd) { + this(fd, null); + } + + /** {@hide} */ + public ParcelFileDescriptor(FileDescriptor fd, FileDescriptor commChannel) { + if (fd == null) { + throw new NullPointerException("FileDescriptor must not be null"); + } + mWrapped = null; + mFd = fd; + mCommFd = commChannel; + mGuard.open("close"); + } + + /** * Create a new ParcelFileDescriptor accessing a given file. * * @param file The file to be opened. * @param mode The desired access mode, must be one of - * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or - * {@link #MODE_READ_WRITE}; may also be any combination of - * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE}, - * {@link #MODE_WORLD_READABLE}, and {@link #MODE_WORLD_WRITEABLE}. - * - * @return Returns a new ParcelFileDescriptor pointing to the given - * file. + * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or + * {@link #MODE_READ_WRITE}; may also be any combination of + * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE}, + * {@link #MODE_WORLD_READABLE}, and + * {@link #MODE_WORLD_WRITEABLE}. + * @return a new ParcelFileDescriptor pointing to the given file. + * @throws FileNotFoundException if the given file does not exist or can not + * be opened with the requested mode. + */ + public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException { + final FileDescriptor fd = openInternal(file, mode); + if (fd == null) return null; + + return new ParcelFileDescriptor(fd); + } + + /** + * Create a new ParcelFileDescriptor accessing a given file. * - * @throws FileNotFoundException Throws FileNotFoundException if the given - * file does not exist or can not be opened with the requested mode. + * @param file The file to be opened. + * @param mode The desired access mode, must be one of + * {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or + * {@link #MODE_READ_WRITE}; may also be any combination of + * {@link #MODE_CREATE}, {@link #MODE_TRUNCATE}, + * {@link #MODE_WORLD_READABLE}, and + * {@link #MODE_WORLD_WRITEABLE}. + * @param handler to call listener from; must not be null. + * @param listener to be invoked when the returned descriptor has been + * closed; must not be null. + * @return a new ParcelFileDescriptor pointing to the given file. + * @throws FileNotFoundException if the given file does not exist or can not + * be opened with the requested mode. */ - public static ParcelFileDescriptor open(File file, int mode) - throws FileNotFoundException { - String path = file.getPath(); + public static ParcelFileDescriptor open( + File file, int mode, Handler handler, OnCloseListener listener) throws IOException { + if (handler == null) { + throw new IllegalArgumentException("Handler must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + + final FileDescriptor fd = openInternal(file, mode); + if (fd == null) return null; + + final FileDescriptor[] comm = createCommSocketPair(true); + final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]); + + // Kick off thread to watch for status updates + final ListenerBridge bridge = new ListenerBridge(comm[1], handler.getLooper(), listener); + bridge.start(); + + return pfd; + } - if ((mode&MODE_READ_WRITE) == 0) { + private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { + if ((mode & MODE_READ_WRITE) == 0) { throw new IllegalArgumentException( "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE"); } - FileDescriptor fd = Parcel.openFileDescriptor(path, mode); - return fd != null ? new ParcelFileDescriptor(fd) : null; + final String path = file.getPath(); + return Parcel.openFileDescriptor(path, mode); } /** @@ -125,8 +256,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * original file descriptor. */ public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException { - FileDescriptor fd = Parcel.dupFileDescriptor(orig); - return fd != null ? new ParcelFileDescriptor(fd) : null; + try { + final FileDescriptor fd = Libcore.os.dup(orig); + return new ParcelFileDescriptor(fd); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } } /** @@ -136,7 +271,11 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * original file descriptor. */ public ParcelFileDescriptor dup() throws IOException { - return dup(getFileDescriptor()); + if (mWrapped != null) { + return mWrapped.dup(); + } else { + return dup(getFileDescriptor()); + } } /** @@ -150,12 +289,16 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * for a dup of the given fd. */ public static ParcelFileDescriptor fromFd(int fd) throws IOException { - FileDescriptor fdesc = getFileDescriptorFromFd(fd); - return new ParcelFileDescriptor(fdesc); - } + final FileDescriptor original = new FileDescriptor(); + original.setInt$(fd); - // Extracts the file descriptor from the specified socket and returns it untouched - private static native FileDescriptor getFileDescriptorFromFd(int fd) throws IOException; + try { + final FileDescriptor dup = Libcore.os.dup(original); + return new ParcelFileDescriptor(dup); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } /** * Take ownership of a raw native fd in to a new ParcelFileDescriptor. @@ -168,13 +311,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * for the given fd. */ public static ParcelFileDescriptor adoptFd(int fd) { - FileDescriptor fdesc = getFileDescriptorFromFdNoDup(fd); + final FileDescriptor fdesc = new FileDescriptor(); + fdesc.setInt$(fd); + return new ParcelFileDescriptor(fdesc); } - // Extracts the file descriptor from the specified socket and returns it untouched - private static native FileDescriptor getFileDescriptorFromFdNoDup(int fd); - /** * Create a new ParcelFileDescriptor from the specified Socket. The new * ParcelFileDescriptor holds a dup of the original FileDescriptor in @@ -212,15 +354,90 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * is the write side. */ public static ParcelFileDescriptor[] createPipe() throws IOException { - FileDescriptor[] fds = new FileDescriptor[2]; - createPipeNative(fds); - ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2]; - pfds[0] = new ParcelFileDescriptor(fds[0]); - pfds[1] = new ParcelFileDescriptor(fds[1]); - return pfds; + try { + final FileDescriptor[] fds = Libcore.os.pipe(); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fds[0]), + new ParcelFileDescriptor(fds[1]) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + /** + * Create two ParcelFileDescriptors structured as a data pipe. The first + * ParcelFileDescriptor in the returned array is the read side; the second + * is the write side. + * <p> + * The write end has the ability to deliver an error message through + * {@link #closeWithError(String)} which can be handled by the read end + * calling {@link #checkError(boolean)}, usually after detecting an EOF. + * This can also be used to detect remote crashes. + */ + public static ParcelFileDescriptor[] createReliablePipe() throws IOException { + try { + final FileDescriptor[] comm = createCommSocketPair(false); + final FileDescriptor[] fds = Libcore.os.pipe(); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fds[0], comm[0]), + new ParcelFileDescriptor(fds[1], comm[1]) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } } - private static native void createPipeNative(FileDescriptor[] outFds) throws IOException; + /** + * Create two ParcelFileDescriptors structured as a pair of sockets + * connected to each other. The two sockets are indistinguishable. + */ + public static ParcelFileDescriptor[] createSocketPair() throws IOException { + try { + final FileDescriptor fd0 = new FileDescriptor(); + final FileDescriptor fd1 = new FileDescriptor(); + Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fd0), + new ParcelFileDescriptor(fd1) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + /** + * Create two ParcelFileDescriptors structured as a pair of sockets + * connected to each other. The two sockets are indistinguishable. + * <p> + * Both ends have the ability to deliver an error message through + * {@link #closeWithError(String)} which can be detected by the other end + * calling {@link #checkError(boolean)}, usually after detecting an EOF. + * This can also be used to detect remote crashes. + */ + public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException { + try { + final FileDescriptor[] comm = createCommSocketPair(false); + final FileDescriptor fd0 = new FileDescriptor(); + final FileDescriptor fd1 = new FileDescriptor(); + Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1); + return new ParcelFileDescriptor[] { + new ParcelFileDescriptor(fd0, comm[0]), + new ParcelFileDescriptor(fd1, comm[1]) }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + private static FileDescriptor[] createCommSocketPair(boolean blocking) throws IOException { + try { + final FileDescriptor comm1 = new FileDescriptor(); + final FileDescriptor comm2 = new FileDescriptor(); + Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2); + IoUtils.setBlocking(comm1, blocking); + IoUtils.setBlocking(comm2, blocking); + return new FileDescriptor[] { comm1, comm2 }; + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } /** * @hide Please use createPipe() or ContentProvider.openPipeHelper(). @@ -250,21 +467,51 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * @return Returns the FileDescriptor associated with this object. */ public FileDescriptor getFileDescriptor() { - return mFileDescriptor; + if (mWrapped != null) { + return mWrapped.getFileDescriptor(); + } else { + return mFd; + } } /** - * Return the total size of the file representing this fd, as determined - * by stat(). Returns -1 if the fd is not a file. + * Return the total size of the file representing this fd, as determined by + * {@code stat()}. Returns -1 if the fd is not a file. */ - public native long getStatSize(); + public long getStatSize() { + if (mWrapped != null) { + return mWrapped.getStatSize(); + } else { + try { + final StructStat st = Libcore.os.fstat(mFd); + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + return st.st_size; + } else { + return -1; + } + } catch (ErrnoException e) { + Log.w(TAG, "fstat() failed: " + e); + return -1; + } + } + } /** * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream, * and I really don't think we want it to be public. * @hide */ - public native long seekTo(long pos); + public long seekTo(long pos) throws IOException { + if (mWrapped != null) { + return mWrapped.seekTo(pos); + } else { + try { + return Libcore.os.lseek(mFd, pos, SEEK_SET); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + } /** * Return the native fd int for this ParcelFileDescriptor. The @@ -272,34 +519,39 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * through this API. */ public int getFd() { - if (mClosed) { - throw new IllegalStateException("Already closed"); + if (mWrapped != null) { + return mWrapped.getFd(); + } else { + if (mClosed) { + throw new IllegalStateException("Already closed"); + } + return mFd.getInt$(); } - return getFdNative(); } - private native int getFdNative(); - /** - * Return the native fd int for this ParcelFileDescriptor and detach it - * from the object here. You are now responsible for closing the fd in - * native code. + * Return the native fd int for this ParcelFileDescriptor and detach it from + * the object here. You are now responsible for closing the fd in native + * code. + * <p> + * You should not detach when the original creator of the descriptor is + * expecting a reliable signal through {@link #close()} or + * {@link #closeWithError(String)}. + * + * @see #canDetectErrors() */ public int detachFd() { - if (mClosed) { - throw new IllegalStateException("Already closed"); - } if (mWrapped != null) { - int fd = mWrapped.detachFd(); - mClosed = true; - mGuard.close(); + return mWrapped.detachFd(); + } else { + if (mClosed) { + throw new IllegalStateException("Already closed"); + } + final int fd = getFd(); + Parcel.clearFileDescriptor(mFd); + writeCommStatusAndClose(Status.DETACHED, null); return fd; } - int fd = getFd(); - mClosed = true; - mGuard.close(); - Parcel.clearFileDescriptor(mFileDescriptor); - return fd; } /** @@ -311,16 +563,176 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ @Override public void close() throws IOException { - if (mClosed) return; - mClosed = true; - mGuard.close(); - if (mWrapped != null) { - // If this is a proxy to another file descriptor, just call through to its - // close method. mWrapped.close(); } else { - Parcel.closeFileDescriptor(mFileDescriptor); + closeWithStatus(Status.OK, null); + } + } + + /** + * Close the ParcelFileDescriptor, informing any peer that an error occurred + * while processing. If the creator of this descriptor is not observing + * errors, it will close normally. + * + * @param msg describing the error; must not be null. + */ + public void closeWithError(String msg) throws IOException { + if (mWrapped != null) { + mWrapped.closeWithError(msg); + } else { + if (msg == null) { + throw new IllegalArgumentException("Message must not be null"); + } + closeWithStatus(Status.ERROR, msg); + } + } + + private void closeWithStatus(int status, String msg) throws IOException { + if (mWrapped != null) { + mWrapped.closeWithStatus(status, msg); + } else { + if (mClosed) return; + mClosed = true; + mGuard.close(); + // Status MUST be sent before closing actual descriptor + writeCommStatusAndClose(status, msg); + IoUtils.closeQuietly(mFd); + } + } + + private byte[] getOrCreateStatusBuffer() { + if (mStatusBuf == null) { + mStatusBuf = new byte[MAX_STATUS]; + } + return mStatusBuf; + } + + private void writeCommStatusAndClose(int status, String msg) { + if (mCommFd == null) { + // Not reliable, or someone already sent status + if (msg != null) { + Log.w(TAG, "Unable to inform peer: " + msg); + } + return; + } + + if (status == Status.DETACHED) { + Log.w(TAG, "Peer expected signal when closed; unable to deliver after detach"); + } + + try { + try { + if (status != Status.SILENCE) { + final byte[] buf = getOrCreateStatusBuffer(); + int writePtr = 0; + + Memory.pokeInt(buf, writePtr, status, ByteOrder.BIG_ENDIAN); + writePtr += 4; + + if (msg != null) { + final byte[] rawMsg = msg.getBytes(); + final int len = Math.min(rawMsg.length, buf.length - writePtr); + System.arraycopy(rawMsg, 0, buf, writePtr, len); + writePtr += len; + } + + Libcore.os.write(mCommFd, buf, 0, writePtr); + } + } catch (ErrnoException e) { + // Reporting status is best-effort + Log.w(TAG, "Failed to report status: " + e); + } + + if (status != Status.SILENCE) { + // Since we're about to close, read off any remote status. It's + // okay to remember missing here. + mStatus = readCommStatus(mCommFd, getOrCreateStatusBuffer()); + } + + } finally { + IoUtils.closeQuietly(mCommFd); + mCommFd = null; + } + } + + private static Status readCommStatus(FileDescriptor comm, byte[] buf) { + try { + final int n = Libcore.os.read(comm, buf, 0, buf.length); + if (n == 0) { + // EOF means they're dead + return new Status(Status.DEAD); + } else { + final int status = Memory.peekInt(buf, 0, ByteOrder.BIG_ENDIAN); + if (status == Status.ERROR) { + final String msg = new String(buf, 4, n - 4); + return new Status(status, msg); + } + return new Status(status); + } + } catch (ErrnoException e) { + if (e.errno == OsConstants.EAGAIN) { + // Remote is still alive, but no status written yet + return null; + } else { + Log.d(TAG, "Failed to read status; assuming dead: " + e); + return new Status(Status.DEAD); + } + } + } + + /** + * Indicates if this ParcelFileDescriptor can communicate and detect remote + * errors/crashes. + * + * @see #checkError(boolean) + */ + public boolean canDetectErrors() { + if (mWrapped != null) { + return mWrapped.canDetectErrors(); + } else { + return mCommFd != null; + } + } + + /** + * Detect and throw if the other end of a pipe or socket pair encountered an + * error or crashed. This allows a reader to distinguish between a valid EOF + * and an error/crash. + * <p> + * If this ParcelFileDescriptor is unable to detect remote errors, it will + * return silently. + * + * @param throwIfDetached requests that an exception be thrown if the remote + * side called {@link #detachFd()}. Once detached, the remote + * side is unable to communicate any errors through + * {@link #closeWithError(String)}. An application may pass true + * if it needs a stronger guarantee that the stream was closed + * normally and was not merely detached. + * @see #canDetectErrors() + */ + public void checkError(boolean throwIfDetached) throws IOException { + if (mWrapped != null) { + mWrapped.checkError(throwIfDetached); + } else { + if (mStatus == null) { + if (mCommFd == null) { + Log.w(TAG, "Peer didn't provide a comm channel; unable to check for errors"); + return; + } + + // Try reading status; it might be null if nothing written yet. + // Either way, we keep comm open to write our status later. + mStatus = readCommStatus(mCommFd, getOrCreateStatusBuffer()); + } + + if (mStatus == null || mStatus.status == Status.OK + || (mStatus.status == Status.DETACHED && !throwIfDetached)) { + // No status yet, or everything is peachy! + return; + } else { + throw mStatus.asIOException(); + } } } @@ -330,17 +742,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseInputStream extends FileInputStream { - private final ParcelFileDescriptor mFd; + private final ParcelFileDescriptor mPfd; - public AutoCloseInputStream(ParcelFileDescriptor fd) { - super(fd.getFileDescriptor()); - mFd = fd; + public AutoCloseInputStream(ParcelFileDescriptor pfd) { + super(pfd.getFileDescriptor()); + mPfd = pfd; } @Override public void close() throws IOException { try { - mFd.close(); + mPfd.close(); } finally { super.close(); } @@ -353,17 +765,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseOutputStream extends FileOutputStream { - private final ParcelFileDescriptor mFd; + private final ParcelFileDescriptor mPfd; - public AutoCloseOutputStream(ParcelFileDescriptor fd) { - super(fd.getFileDescriptor()); - mFd = fd; + public AutoCloseOutputStream(ParcelFileDescriptor pfd) { + super(pfd.getFileDescriptor()); + mPfd = pfd; } @Override public void close() throws IOException { try { - mFd.close(); + mPfd.close(); } finally { super.close(); } @@ -372,7 +784,11 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { @Override public String toString() { - return "{ParcelFileDescriptor: " + mFileDescriptor + "}"; + if (mWrapped != null) { + return mWrapped.toString(); + } else { + return "{ParcelFileDescriptor: " + mFd + "}"; + } } @Override @@ -382,32 +798,20 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } try { if (!mClosed) { - close(); + closeWithStatus(Status.LEAKED, null); } } finally { super.finalize(); } } - public ParcelFileDescriptor(ParcelFileDescriptor descriptor) { - mWrapped = descriptor; - mFileDescriptor = mWrapped.mFileDescriptor; - mGuard.open("close"); - } - - /** {@hide} */ - public ParcelFileDescriptor(FileDescriptor descriptor) { - if (descriptor == null) { - throw new NullPointerException("descriptor must not be null"); - } - mWrapped = null; - mFileDescriptor = descriptor; - mGuard.open("close"); - } - @Override public int describeContents() { - return Parcelable.CONTENTS_FILE_DESCRIPTOR; + if (mWrapped != null) { + return mWrapped.describeContents(); + } else { + return Parcelable.CONTENTS_FILE_DESCRIPTOR; + } } /** @@ -417,12 +821,22 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ @Override public void writeToParcel(Parcel out, int flags) { - out.writeFileDescriptor(mFileDescriptor); - if ((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) { - try { - close(); - } catch (IOException e) { - // Empty + if (mWrapped != null) { + mWrapped.writeToParcel(out, flags); + } else { + out.writeFileDescriptor(mFd); + if (mCommFd != null) { + out.writeInt(1); + out.writeFileDescriptor(mCommFd); + } else { + out.writeInt(0); + } + if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) { + try { + // Not a real close, so emit no status + closeWithStatus(Status.SILENCE, null); + } catch (IOException e) { + } } } } @@ -431,7 +845,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { = new Parcelable.Creator<ParcelFileDescriptor>() { @Override public ParcelFileDescriptor createFromParcel(Parcel in) { - return in.readFileDescriptor(); + final FileDescriptor fd = in.readRawFileDescriptor(); + FileDescriptor commChannel = null; + if (in.readInt() != 0) { + commChannel = in.readRawFileDescriptor(); + } + return new ParcelFileDescriptor(fd, commChannel); } @Override @@ -439,4 +858,111 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return new ParcelFileDescriptor[size]; } }; + + /** + * Callback indicating that a ParcelFileDescriptor has been closed. + */ + public interface OnCloseListener { + /** + * Event indicating the ParcelFileDescriptor to which this listener was + * attached has been closed. + * + * @param e error state, or {@code null} if closed cleanly. + * @param fromDetach indicates if close event was result of + * {@link ParcelFileDescriptor#detachFd()}. After detach the + * remote side may continue reading/writing to the underlying + * {@link FileDescriptor}, but they can no longer deliver + * reliable close/error events. + */ + public void onClose(IOException e, boolean fromDetach); + } + + /** + * Internal class representing a remote status read by + * {@link ParcelFileDescriptor#readCommStatus(FileDescriptor, byte[])}. + */ + private static class Status { + /** Special value indicating remote side died. */ + public static final int DEAD = -2; + /** Special value indicating no status should be written. */ + public static final int SILENCE = -1; + + /** Remote reported that everything went better than expected. */ + public static final int OK = 0; + /** Remote reported error; length and message follow. */ + public static final int ERROR = 1; + /** Remote reported {@link #detachFd()} and went rogue. */ + public static final int DETACHED = 2; + /** Remote reported their object was finalized. */ + public static final int LEAKED = 3; + + public final int status; + public final String msg; + + public Status(int status) { + this(status, null); + } + + public Status(int status, String msg) { + this.status = status; + this.msg = msg; + } + + public IOException asIOException() { + switch (status) { + case DEAD: + return new IOException("Remote side is dead"); + case OK: + return null; + case ERROR: + return new IOException("Remote error: " + msg); + case DETACHED: + return new IOException("Remote side is detached"); + case LEAKED: + return new IOException("Remote side was leaked"); + default: + return new IOException("Unknown status: " + status); + } + } + } + + /** + * Bridge to watch for remote status, and deliver to listener. Currently + * requires that communication socket is <em>blocking</em>. + */ + private static final class ListenerBridge extends Thread { + // TODO: switch to using Looper to avoid burning a thread + + private FileDescriptor mCommFd; + private final Handler mHandler; + + public ListenerBridge(FileDescriptor comm, Looper looper, final OnCloseListener listener) { + mCommFd = comm; + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + final Status s = (Status) msg.obj; + if (s.status == Status.DETACHED) { + listener.onClose(null, true); + } else if (s.status == Status.OK) { + listener.onClose(null, false); + } else { + listener.onClose(s.asIOException(), false); + } + } + }; + } + + @Override + public void run() { + try { + final byte[] buf = new byte[MAX_STATUS]; + final Status status = readCommStatus(mCommFd, buf); + mHandler.obtainMessage(0, status).sendToTarget(); + } finally { + IoUtils.closeQuietly(mCommFd); + mCommFd = null; + } + } + } } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 53f5d58..91d349a 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -36,6 +36,7 @@ import com.google.android.collect.Lists; import libcore.io.IoUtils; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -53,63 +54,85 @@ public final class DocumentsContract { // content://com.example/roots/sdcard/docs/0/contents/ // content://com.example/roots/sdcard/docs/0/search/?query=pony - /** - * MIME type of a document which is a directory that may contain additional - * documents. - * - * @see #buildContentsUri(Uri) - */ - public static final String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc"; - /** {@hide} */ public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER"; - /** - * {@link DocumentColumns#DOC_ID} value representing the root directory of a - * storage root. - */ - public static final String ROOT_DOC_ID = "0"; + /** {@hide} */ + public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED"; - /** - * Flag indicating that a document is a directory that supports creation of - * new files within it. - * - * @see DocumentColumns#FLAGS - * @see #buildContentsUri(Uri) - */ - public static final int FLAG_SUPPORTS_CREATE = 1; + public static class Documents { + private Documents() { + } - /** - * Flag indicating that a document is renamable. - * - * @see DocumentColumns#FLAGS - * @see #renameDocument(ContentResolver, Uri, String) - */ - public static final int FLAG_SUPPORTS_RENAME = 1 << 1; + /** + * MIME type of a document which is a directory that may contain additional + * documents. + * + * @see #buildContentsUri(String, String, String) + */ + public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc"; - /** - * Flag indicating that a document is deletable. - * - * @see DocumentColumns#FLAGS - */ - public static final int FLAG_SUPPORTS_DELETE = 1 << 2; + /** + * {@link DocumentColumns#DOC_ID} value representing the root directory of a + * storage root. + */ + public static final String DOC_ID_ROOT = "0"; - /** - * Flag indicating that a document can be represented as a thumbnail. - * - * @see DocumentColumns#FLAGS - * @see #getThumbnail(ContentResolver, Uri, Point) - */ - public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; + /** + * Flag indicating that a document is a directory that supports creation of + * new files within it. + * + * @see DocumentColumns#FLAGS + * @see #createDocument(ContentResolver, Uri, String, String) + */ + public static final int FLAG_SUPPORTS_CREATE = 1; - /** - * Flag indicating that a document is a directory that supports search. - * - * @see DocumentColumns#FLAGS - */ - public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + /** + * Flag indicating that a document is renamable. + * + * @see DocumentColumns#FLAGS + * @see #renameDocument(ContentResolver, Uri, String) + */ + public static final int FLAG_SUPPORTS_RENAME = 1 << 1; - // TODO: flag indicating that document is writable? + /** + * Flag indicating that a document is deletable. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_SUPPORTS_DELETE = 1 << 2; + + /** + * Flag indicating that a document can be represented as a thumbnail. + * + * @see DocumentColumns#FLAGS + * @see #getThumbnail(ContentResolver, Uri, Point) + */ + public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; + + /** + * Flag indicating that a document is a directory that supports search. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + + /** + * Flag indicating that a document is writable. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_SUPPORTS_WRITE = 1 << 5; + + /** + * Flag indicating that a document is a directory that prefers its contents + * be shown in a larger format grid. Usually suitable when a directory + * contains mostly pictures. + * + * @see DocumentColumns#FLAGS + */ + public static final int FLAG_PREFERS_GRID = 1 << 6; + } /** * Optimal dimensions for a document thumbnail request, stored as a @@ -144,7 +167,8 @@ public final class DocumentsContract { private static final String PATH_CONTENTS = "contents"; private static final String PATH_SEARCH = "search"; - public static final String PARAM_QUERY = "query"; + private static final String PARAM_QUERY = "query"; + private static final String PARAM_LOCAL_ONLY = "localOnly"; /** * Build URI representing the roots in a storage backend. @@ -171,7 +195,7 @@ public final class DocumentsContract { /** * Build URI representing the contents of the given directory in a storage - * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}. + * backend. The given document must be {@link Documents#MIME_TYPE_DIR}. */ public static Uri buildContentsUri(String authority, String rootId, String docId) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) @@ -228,11 +252,32 @@ public final class DocumentsContract { return paths.get(3); } + /** + * Return requested search query from the given Uri. + */ public static String getSearchQuery(Uri documentUri) { return documentUri.getQueryParameter(PARAM_QUERY); } /** + * Mark the given Uri to indicate that only locally-available contents + * should be returned. + */ + public static Uri setLocalOnly(Uri documentUri) { + return documentUri.buildUpon() + .appendQueryParameter(PARAM_LOCAL_ONLY, String.valueOf(true)).build(); + } + + /** + * Return if the given Uri is requesting that only locally-available content + * be returned. That is, no network connections should be initiated to + * provide the metadata or content. + */ + public static boolean isLocalOnly(Uri documentUri) { + return documentUri.getBooleanQueryParameter(PARAM_LOCAL_ONLY, false); + } + + /** * These are standard columns for document URIs. Storage backend providers * <em>must</em> support at least these columns when queried. * @@ -257,7 +302,7 @@ public final class DocumentsContract { * <p> * Type: STRING * - * @see DocumentsContract#MIME_TYPE_DIRECTORY + * @see Documents#MIME_TYPE_DIR */ public static final String MIME_TYPE = "mime_type"; @@ -288,35 +333,43 @@ public final class DocumentsContract { public static final String SUMMARY = "summary"; } - /** - * Root that represents a cloud-based storage service. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_SERVICE = 1; + public static class Roots { + private Roots() { + } - /** - * Root that represents a shortcut to content that may be available - * elsewhere through another storage root. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_SHORTCUT = 2; + public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/root"; + public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root"; - /** - * Root that represents a physical storage device. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_DEVICE = 3; + /** + * Root that represents a cloud-based storage service. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_SERVICE = 1; - /** - * Root that represents a physical storage device that should only be - * displayed to advanced users. - * - * @see RootColumns#ROOT_TYPE - */ - public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; + /** + * Root that represents a shortcut to content that may be available + * elsewhere through another storage root. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_SHORTCUT = 2; + + /** + * Root that represents a physical storage device. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_DEVICE = 3; + + /** + * Root that represents a physical storage device that should only be + * displayed to advanced users. + * + * @see RootColumns#ROOT_TYPE + */ + public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; + } /** * These are standard columns for the roots URI. @@ -331,8 +384,8 @@ public final class DocumentsContract { * <p> * Type: INTEGER (int) * - * @see DocumentsContract#ROOT_TYPE_SERVICE - * @see DocumentsContract#ROOT_TYPE_DEVICE + * @see Roots#ROOT_TYPE_SERVICE + * @see Roots#ROOT_TYPE_DEVICE */ public static final String ROOT_TYPE = "root_type"; @@ -401,7 +454,7 @@ public final class DocumentsContract { /** * Return thumbnail representing the document at the given URI. Callers are * responsible for their own caching. Given document must have - * {@link #FLAG_SUPPORTS_THUMBNAIL} set. + * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set. * * @return decoded thumbnail, or {@code null} if problem was encountered. */ @@ -409,22 +462,51 @@ public final class DocumentsContract { final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size); - InputStream is = null; + AssetFileDescriptor afd = null; try { - is = new AssetFileDescriptor.AutoCloseInputStream( - resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts)); - return BitmapFactory.decodeStream(is); + afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts); + + final FileDescriptor fd = afd.getFileDescriptor(); + final BitmapFactory.Options bitmapOpts = new BitmapFactory.Options(); + + bitmapOpts.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); + + final int widthSample = bitmapOpts.outWidth / size.x; + final int heightSample = bitmapOpts.outHeight / size.y; + + bitmapOpts.inJustDecodeBounds = false; + bitmapOpts.inSampleSize = Math.min(widthSample, heightSample); + return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); } catch (IOException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; } finally { - IoUtils.closeQuietly(is); + IoUtils.closeQuietly(afd); } } /** + * Create a new document under a specific parent document with the given + * display name and MIME type. + * + * @param parentDocumentUri document with + * {@link Documents#FLAG_SUPPORTS_CREATE} + * @param displayName name for new document + * @param mimeType MIME type for new document, which cannot be changed + * @return newly created document Uri, or {@code null} if failed + */ + public static Uri createDocument( + ContentResolver resolver, Uri parentDocumentUri, String displayName, String mimeType) { + final ContentValues values = new ContentValues(); + values.put(DocumentColumns.MIME_TYPE, mimeType); + values.put(DocumentColumns.DISPLAY_NAME, displayName); + return resolver.insert(parentDocumentUri, values); + } + + /** * Rename the document at the given URI. Given document must have - * {@link #FLAG_SUPPORTS_RENAME} set. + * {@link Documents#FLAG_SUPPORTS_RENAME} set. * * @return if rename was successful. */ @@ -434,4 +516,14 @@ public final class DocumentsContract { values.put(DocumentColumns.DISPLAY_NAME, displayName); return (resolver.update(documentUri, values, null, null) == 1); } + + /** + * Notify the system that roots have changed for the given storage provider. + * This signal is used to invalidate internal caches. + */ + public static void notifyRootsChanged(Context context, String authority) { + final Intent intent = new Intent(ACTION_DOCUMENT_CHANGED); + intent.setData(buildRootsUri(authority)); + context.sendBroadcast(intent); + } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index cb6300f..ad6839b 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -19,8 +19,8 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; @@ -532,7 +532,8 @@ public final class MediaStore { private static final Object sThumbBufLock = new Object(); private static byte[] sThumbBuf; - private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { + private static Bitmap getMiniThumbFromFile( + Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { Bitmap bitmap = null; Uri thumbUri = null; try { @@ -577,6 +578,7 @@ public final class MediaStore { if (c != null) c.close(); } } + /** * This method ensure thumbnails associated with origId are generated and decode the byte * stream from database (MICRO_KIND) or file (MINI_KIND). diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index cba350f..22675b4 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -815,7 +815,14 @@ public class DateUtils * @return the formatter with the formatted date/time range appended to the string buffer. */ public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, - long endMillis, int flags, String timeZone) { + long endMillis, int flags, String timeZone) { + // If we're being asked to format a time without being explicitly told whether to use + // the 12- or 24-hour clock, icu4c will fall back to the locale's preferred 12/24 format, + // but we want to fall back to the user's preference. + if ((flags & (FORMAT_SHOW_TIME | FORMAT_12HOUR | FORMAT_24HOUR)) == FORMAT_SHOW_TIME) { + flags |= DateFormat.is24HourFormat(context) ? FORMAT_24HOUR : FORMAT_12HOUR; + } + String range = DateIntervalFormat.formatDateRange(startMillis, endMillis, flags, timeZone); try { formatter.out().append(range); diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 2d24c1e..43fd628 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -208,9 +208,22 @@ public abstract class DisplayList { * {@link #isValid()} will return false. * * @see #isValid() + * @see #reset() */ public abstract void clear(); + + /** + * Reset native resources. This is called when cleaning up the state of display lists + * during destruction of hardware resources, to ensure that we do not hold onto + * obsolete resources after related resources are gone. + * + * @see #clear() + * + * @hide + */ + public abstract void reset(); + /** * Sets the dirty flag. When a display list is dirty, {@link #clear()} should * be invoked whenever possible. @@ -670,13 +683,4 @@ public abstract class DisplayList { * @see View#offsetTopAndBottom(int) */ public abstract void offsetTopAndBottom(float offset); - - /** - * Reset native resources. This is called when cleaning up the state of display lists - * during destruction of hardware resources, to ensure that we do not hold onto - * obsolete resources after related resources are gone. - * - * @hide - */ - public abstract void reset(); } diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 8b2a2ef..c652bac 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -94,6 +94,7 @@ class GLES20DisplayList extends DisplayList { if (hasNativeDisplayList()) { nReset(mFinalizer.mNativeDisplayList); } + clear(); } @Override diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java index 7ee628b..0e3311c 100644 --- a/core/java/android/view/GLES20Layer.java +++ b/core/java/android/view/GLES20Layer.java @@ -59,6 +59,9 @@ abstract class GLES20Layer extends HardwareLayer { @Override public void destroy() { + if (mDisplayList != null) { + mDisplayList.reset(); + } if (mFinalizer != null) { mFinalizer.destroy(); mFinalizer = null; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c0db23c..f05e372 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4572,10 +4572,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " clearFocus()"); } + clearFocusInternal(true, true); + } + + /** + * Clears focus from the view, optionally propagating the change up through + * the parent hierarchy and requesting that the root view place new focus. + * + * @param propagate whether to propagate the change up through the parent + * hierarchy + * @param refocus when propagate is true, specifies whether to request the + * root view place new focus + */ + void clearFocusInternal(boolean propagate, boolean refocus) { if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { mPrivateFlags &= ~PFLAG_FOCUSED; - if (mParent != null) { + if (propagate && mParent != null) { mParent.clearChildFocus(this); } @@ -4583,7 +4596,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, refreshDrawableState(); - if (!rootViewRequestFocus()) { + if (propagate && (!refocus || !rootViewRequestFocus())) { notifyGlobalFocusCleared(this); } } @@ -4613,12 +4626,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " unFocus()"); } - if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { - mPrivateFlags &= ~PFLAG_FOCUSED; - - onFocusChanged(false, 0, null); - refreshDrawableState(); - } + clearFocusInternal(false, false); } /** @@ -12068,12 +12076,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void resolvePadding() { + final int resolvedLayoutDirection = getLayoutDirection(); + if (!isRtlCompatibilityMode()) { // Post Jelly Bean MR1 case: we need to take the resolved layout direction into account. // If start / end padding are defined, they will be resolved (hence overriding) to // left / right or right / left depending on the resolved layout direction. // If start / end padding are not defined, use the left / right ones. - int resolvedLayoutDirection = getLayoutDirection(); switch (resolvedLayoutDirection) { case LAYOUT_DIRECTION_RTL: if (mUserPaddingStart != UNDEFINED_PADDING) { @@ -12102,11 +12111,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom; - - onRtlPropertiesChanged(resolvedLayoutDirection); } internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); + onRtlPropertiesChanged(resolvedLayoutDirection); mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED; } @@ -12136,7 +12144,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeSendViewScrolledAccessibilityEventCallback(); destroyDrawingCache(); - destroyLayer(false); cleanupDraw(); @@ -12154,7 +12161,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mViewRootImpl.cancelInvalidate(this); } else { // Should never happen - clearDisplayList(); + resetDisplayList(); } } @@ -12765,9 +12772,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mHardwareLayer.destroy(); mHardwareLayer = null; - if (mDisplayList != null) { - mDisplayList.reset(); - } invalidate(true); invalidateParentCaches(); } @@ -12788,7 +12792,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected void destroyHardwareResources() { - clearDisplayList(); + resetDisplayList(); destroyLayer(true); } @@ -13036,6 +13040,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private void resetDisplayList() { + if (mDisplayList != null) { + mDisplayList.reset(); + } + } + /** * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p> * @@ -15436,6 +15446,90 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Transforms a motion event from view-local coordinates to on-screen + * coordinates. + * + * @param ev the view-local motion event + * @return false if the transformation could not be applied + * @hide + */ + public boolean toGlobalMotionEvent(MotionEvent ev) { + final AttachInfo info = mAttachInfo; + if (info == null) { + return false; + } + + transformMotionEventToGlobal(ev); + ev.offsetLocation(info.mWindowLeft, info.mWindowTop); + return true; + } + + /** + * Transforms a motion event from on-screen coordinates to view-local + * coordinates. + * + * @param ev the on-screen motion event + * @return false if the transformation could not be applied + * @hide + */ + public boolean toLocalMotionEvent(MotionEvent ev) { + final AttachInfo info = mAttachInfo; + if (info == null) { + return false; + } + + ev.offsetLocation(-info.mWindowLeft, -info.mWindowTop); + transformMotionEventToLocal(ev); + return true; + } + + /** + * Recursive helper method that applies transformations in post-order. + * + * @param ev the on-screen motion event + */ + private void transformMotionEventToLocal(MotionEvent ev) { + final ViewParent parent = mParent; + if (parent instanceof View) { + final View vp = (View) parent; + vp.transformMotionEventToLocal(ev); + ev.offsetLocation(vp.mScrollX, vp.mScrollY); + } else if (parent instanceof ViewRootImpl) { + final ViewRootImpl vr = (ViewRootImpl) parent; + ev.offsetLocation(0, vr.mCurScrollY); + } + + ev.offsetLocation(-mLeft, -mTop); + + if (!hasIdentityMatrix()) { + ev.transform(getInverseMatrix()); + } + } + + /** + * Recursive helper method that applies transformations in pre-order. + * + * @param ev the on-screen motion event + */ + private void transformMotionEventToGlobal(MotionEvent ev) { + if (!hasIdentityMatrix()) { + ev.transform(getMatrix()); + } + + ev.offsetLocation(mLeft, mTop); + + final ViewParent parent = mParent; + if (parent instanceof View) { + final View vp = (View) parent; + ev.offsetLocation(-vp.mScrollX, -vp.mScrollY); + vp.transformMotionEventToGlobal(ev); + } else if (parent instanceof ViewRootImpl) { + final ViewRootImpl vr = (ViewRootImpl) parent; + ev.offsetLocation(0, -vr.mCurScrollY); + } + } + + /** * <p>Computes the coordinates of this view on the screen. The argument * must be an array of two integers. After the method returns, the array * contains the x and y location in that order.</p> diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f574efa..c874c82 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5357,6 +5357,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Returns whether layout calls on this container are currently being + * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}. + * + * @return true if layout calls are currently suppressed, false otherwise. + * + * @hide + */ + public boolean isLayoutSuppressed() { + return mSuppressLayout; + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3977a33..f711e7a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -614,15 +614,7 @@ public final class ViewRootImpl implements ViewParent, } void destroyHardwareResources() { - if (mAttachInfo.mHardwareRenderer != null) { - if (mAttachInfo.mHardwareRenderer.isEnabled()) { - mAttachInfo.mHardwareRenderer.destroyLayers(mView); - } - mAttachInfo.mHardwareRenderer.destroy(false); - } - } - - void terminateHardwareResources() { + invalidateDisplayLists(); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView); mAttachInfo.mHardwareRenderer.destroy(false); @@ -636,6 +628,7 @@ public final class ViewRootImpl implements ViewParent, HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE); } } else { + invalidateDisplayLists(); if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { mAttachInfo.mHardwareRenderer.destroyLayers(mView); @@ -2554,7 +2547,7 @@ public final class ViewRootImpl implements ViewParent, for (int i = 0; i < count; i++) { final DisplayList displayList = displayLists.get(i); if (displayList.isDirty()) { - displayList.clear(); + displayList.reset(); } } @@ -3271,9 +3264,9 @@ public final class ViewRootImpl implements ViewParent, // focus return ancestorToTakeFocus.requestFocus(); } else { - // nothing appropriate to have focus in touch mode, clear it - // out - focused.clearFocus(); + // There's nothing to focus. Clear and propagate through the + // hierarchy, but don't attempt to place new focus. + focused.clearFocusInternal(true, false); return true; } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index b183bb6..3dd96f5 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -21,7 +21,6 @@ import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.res.Configuration; import android.opengl.ManagedEGLContext; -import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -29,7 +28,6 @@ import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.Log; -import android.util.Slog; import android.view.inputmethod.InputMethodManager; import com.android.internal.util.FastPrintWriter; @@ -385,7 +383,7 @@ public final class WindowManagerGlobal { // known windows synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { - mRoots.get(i).terminateHardwareResources(); + mRoots.get(i).destroyHardwareResources(); } } // Force a full memory flush diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index feaab3e..c440c7b 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -82,11 +82,16 @@ public final class InputMethodInfo implements Parcelable { private final boolean mIsAuxIme; /** - * Cavert: mForceDefault must be false for production. This flag is only for test. + * Caveat: mForceDefault must be false for production. This flag is only for test. */ private final boolean mForceDefault; /** + * The flag whether this IME supports ways to switch to a next input method (e.g. globe key.) + */ + private final boolean mSupportsSwitchingToNextInputMethod; + + /** * Constructor. * * @param context The Context in which we are parsing the input method. @@ -114,6 +119,7 @@ public final class InputMethodInfo implements Parcelable { ServiceInfo si = service.serviceInfo; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); boolean isAuxIme = true; + boolean supportsSwitchingToNextInputMethod = false; // false as default mForceDefault = false; PackageManager pm = context.getPackageManager(); @@ -149,6 +155,9 @@ public final class InputMethodInfo implements Parcelable { com.android.internal.R.styleable.InputMethod_settingsActivity); isDefaultResId = sa.getResourceId( com.android.internal.R.styleable.InputMethod_isDefault, 0); + supportsSwitchingToNextInputMethod = sa.getBoolean( + com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod, + false); sa.recycle(); final int depth = parser.getDepth(); @@ -216,6 +225,7 @@ public final class InputMethodInfo implements Parcelable { mSettingsActivityName = settingsActivityComponent; mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; + mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; } InputMethodInfo(Parcel source) { @@ -223,6 +233,7 @@ public final class InputMethodInfo implements Parcelable { mSettingsActivityName = source.readString(); mIsDefaultResId = source.readInt(); mIsAuxIme = source.readInt() == 1; + mSupportsSwitchingToNextInputMethod = source.readInt() == 1; mService = ResolveInfo.CREATOR.createFromParcel(source); source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR); mForceDefault = false; @@ -254,6 +265,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes.addAll(subtypes); } mForceDefault = forceDefault; + mSupportsSwitchingToNextInputMethod = true; } private static ResolveInfo buildDummyResolveInfo(String packageName, String className, @@ -435,6 +447,14 @@ public final class InputMethodInfo implements Parcelable { } /** + * @return true if this input method supports ways to switch to a next input method. + * @hide + */ + public boolean supportsSwitchingToNextInputMethod() { + return mSupportsSwitchingToNextInputMethod; + } + + /** * Used to package this object into a {@link Parcel}. * * @param dest The {@link Parcel} to be written. @@ -446,6 +466,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeString(mSettingsActivityName); dest.writeInt(mIsDefaultResId); dest.writeInt(mIsAuxIme ? 1 : 0); + dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); mService.writeToParcel(dest, flags); dest.writeTypedList(mSubtypes); } diff --git a/core/java/android/view/transition/Fade.java b/core/java/android/view/transition/Fade.java index 4fd60c1..45c21d8 100644 --- a/core/java/android/view/transition/Fade.java +++ b/core/java/android/view/transition/Fade.java @@ -19,6 +19,7 @@ package android.view.transition; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -35,6 +36,7 @@ public class Fade extends Visibility { private static boolean DBG = Transition.DBG && false; private static final String LOG_TAG = "Fade"; + private static final String PROPNAME_ALPHA = "android:fade:alpha"; private static final String PROPNAME_SCREEN_X = "android:fade:screenX"; private static final String PROPNAME_SCREEN_Y = "android:fade:screenY"; @@ -74,20 +76,28 @@ public class Fade extends Visibility { /** * Utility method to handle creating and running the Animator. */ - private Animator runAnimation(View view, float startAlpha, float endAlpha, - Animator.AnimatorListener listener) { + private Animator createAnimation(View view, float startAlpha, float endAlpha, + AnimatorListenerAdapter listener) { + if (startAlpha == endAlpha) { + // run listener if we're noop'ing the animation, to get the end-state results now + if (listener != null) { + listener.onAnimationEnd(null); + } + return null; + } final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", startAlpha, endAlpha); if (listener != null) { anim.addListener(listener); + anim.addPauseListener(listener); } - // TODO: Maybe extract a method into Transition to run an animation that handles the - // duration/startDelay stuff for all subclasses. return anim; } @Override protected void captureValues(TransitionValues values, boolean start) { super.captureValues(values, start); + float alpha = values.view.getAlpha(); + values.values.put(PROPNAME_ALPHA, alpha); int[] loc = new int[2]; values.view.getLocationOnScreen(loc); values.values.put(PROPNAME_SCREEN_X, loc[0]); @@ -95,6 +105,23 @@ public class Fade extends Visibility { } @Override + protected Animator play(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + Animator animator = super.play(sceneRoot, startValues, endValues); + if (animator == null && startValues != null && endValues != null) { + boolean endVisible = isVisible(endValues); + final View endView = endValues.view; + float endAlpha = endView.getAlpha(); + float startAlpha = (Float) startValues.values.get(PROPNAME_ALPHA); + if ((endVisible && startAlpha < endAlpha && (mFadingMode & Fade.IN) != 0) || + (!endVisible && startAlpha > endAlpha && (mFadingMode & Fade.OUT) != 0)) { + animator = createAnimation(endView, startAlpha, endAlpha, null); + } + } + return animator; + } + + @Override protected Animator appear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility) { @@ -102,15 +129,11 @@ public class Fade extends Visibility { return null; } final View endView = endValues.view; - endView.setAlpha(0); - final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Always end animation with full alpha, in case it's canceled mid-stream - endView.setAlpha(1); - } - }; - return runAnimation(endView, 0, 1, endListener); + // if alpha < 1, just fade it in from the current value + if (endView.getAlpha() == 1.0f) { + endView.setAlpha(0); + } + return createAnimation(endView, endView.getAlpha(), 1, null); } @Override @@ -129,7 +152,7 @@ public class Fade extends Visibility { } View overlayView = null; View viewToKeep = null; - if (endView == null) { + if (endView == null || endView.getParent() == null) { // view was removed: add the start view to the Overlay view = startView; overlayView = view; @@ -167,7 +190,7 @@ public class Fade extends Visibility { final View finalOverlayView = overlayView; final View finalViewToKeep = viewToKeep; final ViewGroup finalSceneRoot = sceneRoot; - final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() { + final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finalView.setAlpha(startAlpha); @@ -179,8 +202,22 @@ public class Fade extends Visibility { finalSceneRoot.getOverlay().remove(finalOverlayView); } } + + @Override + public void onAnimationPause(Animator animation) { + if (finalOverlayView != null) { + finalSceneRoot.getOverlay().remove(finalOverlayView); + } + } + + @Override + public void onAnimationResume(Animator animation) { + if (finalOverlayView != null) { + finalSceneRoot.getOverlay().add(finalOverlayView); + } + } }; - return runAnimation(view, startAlpha, endAlpha, endListener); + return createAnimation(view, startAlpha, endAlpha, endListener); } if (viewToKeep != null) { // TODO: find a different way to do this, like just changing the view to be @@ -193,12 +230,42 @@ public class Fade extends Visibility { final View finalOverlayView = overlayView; final View finalViewToKeep = viewToKeep; final ViewGroup finalSceneRoot = sceneRoot; - final Animator.AnimatorListener endListener = new AnimatorListenerAdapter() { + final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { + boolean mCanceled = false; + float mPausedAlpha = -1; + @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationPause(Animator animation) { + if (finalViewToKeep != null && !mCanceled) { + finalViewToKeep.setVisibility(finalVisibility); + } + mPausedAlpha = finalView.getAlpha(); finalView.setAlpha(startAlpha); + } + + @Override + public void onAnimationResume(Animator animation) { + if (finalViewToKeep != null && !mCanceled) { + finalViewToKeep.setVisibility(View.VISIBLE); + } + finalView.setAlpha(mPausedAlpha); + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + if (mPausedAlpha >= 0) { + finalView.setAlpha(mPausedAlpha); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCanceled) { + finalView.setAlpha(startAlpha); + } // TODO: restore view offset from overlay repositioning - if (finalViewToKeep != null) { + if (finalViewToKeep != null && !mCanceled) { finalViewToKeep.setVisibility(finalVisibility); } if (finalOverlayView != null) { @@ -206,7 +273,7 @@ public class Fade extends Visibility { } } }; - return runAnimation(view, startAlpha, endAlpha, endListener); + return createAnimation(view, startAlpha, endAlpha, endListener); } return null; } diff --git a/core/java/android/view/transition/Move.java b/core/java/android/view/transition/Move.java index ceda5a5..ae7d759 100644 --- a/core/java/android/view/transition/Move.java +++ b/core/java/android/view/transition/Move.java @@ -25,8 +25,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; -import android.util.ArrayMap; -import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -42,6 +40,13 @@ public class Move extends Transition { private static final String PROPNAME_PARENT = "android:move:parent"; private static final String PROPNAME_WINDOW_X = "android:move:windowX"; private static final String PROPNAME_WINDOW_Y = "android:move:windowY"; + private static String[] sTransitionProperties = { + PROPNAME_BOUNDS, + PROPNAME_PARENT, + PROPNAME_WINDOW_X, + PROPNAME_WINDOW_Y + }; + int[] tempLocation = new int[2]; boolean mResizeClip = false; boolean mReparent = false; @@ -49,6 +54,11 @@ public class Move extends Transition { private static RectEvaluator sRectEvaluator = new RectEvaluator(); + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + public void setResizeClip(boolean resizeClip) { mResizeClip = resizeClip; } @@ -146,12 +156,33 @@ public class Move extends Transition { if (view.getParent() instanceof ViewGroup) { final ViewGroup parent = (ViewGroup) view.getParent(); parent.suppressLayout(true); - anim.addListener(new AnimatorListenerAdapter() { + TransitionListener transitionListener = new TransitionListenerAdapter() { + boolean mCanceled = false; + @Override - public void onAnimationEnd(Animator animation) { + public void onTransitionCancel(Transition transition) { parent.suppressLayout(false); + mCanceled = true; + } + + @Override + public void onTransitionEnd(Transition transition) { + if (!mCanceled) { + parent.suppressLayout(false); + } + } + + @Override + public void onTransitionPause(Transition transition) { + parent.suppressLayout(false); + } + + @Override + public void onTransitionResume(Transition transition) { + parent.suppressLayout(true); } - }); + }; + addListener(transitionListener); } return anim; } else { @@ -191,12 +222,33 @@ public class Move extends Transition { if (view.getParent() instanceof ViewGroup) { final ViewGroup parent = (ViewGroup) view.getParent(); parent.suppressLayout(true); - anim.addListener(new AnimatorListenerAdapter() { + TransitionListener transitionListener = new TransitionListenerAdapter() { + boolean mCanceled = false; + @Override - public void onAnimationEnd(Animator animation) { + public void onTransitionCancel(Transition transition) { parent.suppressLayout(false); + mCanceled = true; + } + + @Override + public void onTransitionEnd(Transition transition) { + if (!mCanceled) { + parent.suppressLayout(false); + } + } + + @Override + public void onTransitionPause(Transition transition) { + parent.suppressLayout(false); + } + + @Override + public void onTransitionResume(Transition transition) { + parent.suppressLayout(true); } - }); + }; + addListener(transitionListener); } anim.addListener(new AnimatorListenerAdapter() { @Override diff --git a/core/java/android/view/transition/Transition.java b/core/java/android/view/transition/Transition.java index f99ddc0..0444843 100644 --- a/core/java/android/view/transition/Transition.java +++ b/core/java/android/view/transition/Transition.java @@ -22,7 +22,6 @@ import android.animation.TimeInterpolator; import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; -import android.util.Pair; import android.util.SparseArray; import android.view.SurfaceView; import android.view.TextureView; @@ -60,6 +59,8 @@ public abstract class Transition implements Cloneable { private static final String LOG_TAG = "Transition"; static final boolean DBG = false; + private String mName = getClass().getName(); + long mStartDelay = -1; long mDuration = -1; TimeInterpolator mInterpolator = null; @@ -69,29 +70,29 @@ public abstract class Transition implements Cloneable { private TransitionValuesMaps mEndValues = new TransitionValuesMaps(); TransitionGroup mParent = null; + // Per-animator information used for later canceling when future transitions overlap + private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators = + new ThreadLocal<ArrayMap<Animator, AnimationInfo>>(); + // Scene Root is set at play() time in the cloned Transition ViewGroup mSceneRoot = null; - // Used to carry data between setup() and play(), cleared before every scene transition - private ArrayList<TransitionValues> mPlayStartValuesList = new ArrayList<TransitionValues>(); - private ArrayList<TransitionValues> mPlayEndValuesList = new ArrayList<TransitionValues>(); - // Track all animators in use in case the transition gets canceled and needs to // cancel running animators private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>(); // Number of per-target instances of this Transition currently running. This count is - // determined by calls to startTransition() and endTransition() + // determined by calls to start() and end() int mNumInstances = 0; - + // Whether this transition is currently paused, due to a call to pause() + boolean mPaused = false; // The set of listeners to be sent transition lifecycle events. ArrayList<TransitionListener> mListeners = null; // The set of animators collected from calls to play(), to be run in runAnimations() - ArrayMap<Pair<TransitionValues, TransitionValues>, Animator> mAnimatorMap = - new ArrayMap<Pair<TransitionValues, TransitionValues>, Animator>(); + ArrayList<Animator> mAnimators = new ArrayList<Animator>(); /** * Constructs a Transition object with no target objects. A transition with @@ -115,6 +116,14 @@ public abstract class Transition implements Cloneable { return this; } + /** + * Returns the duration set on this transition. If no duration has been set, + * the returned value will be negative, indicating that resulting animators will + * retain their own durations. + * + * @return The duration set on this transition, if one has been set, otherwise + * returns a negative number. + */ public long getDuration() { return mDuration; } @@ -131,6 +140,14 @@ public abstract class Transition implements Cloneable { mStartDelay = startDelay; } + /** + * Returns the startDelay set on this transition. If no startDelay has been set, + * the returned value will be negative, indicating that resulting animators will + * retain their own startDelays. + * + * @return The startDealy set on this transition, if one has been set, otherwise + * returns a negative number. + */ public long getStartDelay() { return mStartDelay; } @@ -147,11 +164,44 @@ public abstract class Transition implements Cloneable { mInterpolator = interpolator; } + /** + * Returns the interpolator set on this transition. If no interpolator has been set, + * the returned value will be null, indicating that resulting animators will + * retain their own interpolators. + * + * @return The interpolator set on this transition, if one has been set, otherwise + * returns null. + */ public TimeInterpolator getInterpolator() { return mInterpolator; } /** + * Returns the set of property names used stored in the {@link TransitionValues} + * object passed into {@link #captureValues(TransitionValues, boolean)} that + * this transition cares about for the purposes of canceling overlapping animations. + * When any transition is started on a given scene root, all transitions + * currently running on that same scene root are checked to see whether the + * properties on which they based their animations agree with the end values of + * the same properties in the new transition. If the end values are not equal, + * then the old animation is canceled since the new transition will start a new + * animation to these new values. If the values are equal, the old animation is + * allowed to continue and no new animation is started for that transition. + * + * <p>A transition does not need to override this method. However, not doing so + * will mean that the cancellation logic outlined in the previous paragraph + * will be skipped for that transition, possibly leading to artifacts as + * old transitions and new transitions on the same targets run in parallel, + * animating views toward potentially different end values.</p> + * + * @return An array of property names as described in the class documentation for + * {@link TransitionValues}. The default implementation returns <code>null</code>. + */ + public String[] getTransitionProperties() { + return null; + } + + /** * This method is called by the transition's parent (all the way up to the * topmost Transition in the hierarchy) with the sceneRoot and start/end * values that the transition may need to set up initial target values @@ -210,8 +260,6 @@ public abstract class Transition implements Cloneable { if (DBG) { Log.d(LOG_TAG, "play() for " + this); } - mPlayStartValuesList.clear(); - mPlayEndValuesList.clear(); ArrayMap<View, TransitionValues> endCopy = new ArrayMap<View, TransitionValues>(endValues.viewValues); SparseArray<TransitionValues> endIdCopy = @@ -316,6 +364,7 @@ public abstract class Transition implements Cloneable { startValuesList.add(start); endValuesList.add(end); } + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); for (int i = 0; i < startValuesList.size(); ++i) { TransitionValues start = startValuesList.get(i); TransitionValues end = endValuesList.get(i); @@ -345,14 +394,46 @@ public abstract class Transition implements Cloneable { // TODO: what to do about targetIds and itemIds? Animator animator = play(sceneRoot, start, end); if (animator != null) { - mAnimatorMap.put(new Pair(start, end), animator); - // Note: we've already done the check against targetIDs in these lists - mPlayStartValuesList.add(start); - mPlayEndValuesList.add(end); + // Save animation info for future cancellation purposes + View view = null; + TransitionValues infoValues = null; + if (end != null) { + view = end.view; + String[] properties = getTransitionProperties(); + if (view != null && properties != null && properties.length > 0) { + infoValues = new TransitionValues(); + infoValues.view = view; + TransitionValues newValues = endValues.viewValues.get(view); + if (newValues != null) { + for (int j = 0; j < properties.length; ++j) { + infoValues.values.put(properties[j], + newValues.values.get(properties[j])); + } + } + int numExistingAnims = runningAnimators.size(); + for (int j = 0; j < numExistingAnims; ++j) { + Animator anim = runningAnimators.keyAt(j); + AnimationInfo info = runningAnimators.get(anim); + if (info.values != null && info.view == view && + ((info.name == null && getName() == null) || + info.name.equals(getName()))) { + if (info.values.equals(infoValues)) { + // Favor the old animator + animator = null; + break; + } + } + } + } + } else { + view = (start != null) ? start.view : null; + } + if (animator != null) { + AnimationInfo info = new AnimationInfo(view, getName(), infoValues); + runningAnimators.put(animator, info); + mAnimators.add(animator); + } } - } else if (DBG) { - View view = (end != null) ? end.view : start.view; - Log.d(LOG_TAG, " No change for view " + view); } } } @@ -389,6 +470,15 @@ public abstract class Transition implements Cloneable { return false; } + private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { + ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get(); + if (runningAnimators == null) { + runningAnimators = new ArrayMap<Animator, AnimationInfo>(); + sRunningAnimators.set(runningAnimators); + } + return runningAnimators; + } + /** * This is called internally once all animations have been set up by the * transition hierarchy. \ @@ -396,28 +486,27 @@ public abstract class Transition implements Cloneable { * @hide */ protected void runAnimations() { - if (DBG && mPlayStartValuesList.size() > 0) { - Log.d(LOG_TAG, "runAnimations (" + mPlayStartValuesList.size() + ") on " + this); - } - startTransition(); - // Now walk the list of TransitionValues, calling play for each pair - for (int i = 0; i < mPlayStartValuesList.size(); ++i) { - TransitionValues start = mPlayStartValuesList.get(i); - TransitionValues end = mPlayEndValuesList.get(i); - Animator anim = mAnimatorMap.get(new Pair(start, end)); + if (DBG) { + Log.d(LOG_TAG, "runAnimations() on " + this); + } + start(); + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); + // Now start every Animator that was previously created for this transition in play() + for (Animator anim : mAnimators) { if (DBG) { Log.d(LOG_TAG, " anim: " + anim); } - startTransition(); - runAnimator(anim); + if (runningAnimators.containsKey(anim)) { + start(); + runAnimator(anim, runningAnimators); + } } - mPlayStartValuesList.clear(); - mPlayEndValuesList.clear(); - mAnimatorMap.clear(); - endTransition(); + mAnimators.clear(); + end(); } - private void runAnimator(Animator animator) { + private void runAnimator(Animator animator, + final ArrayMap<Animator, AnimationInfo> runningAnimators) { if (animator != null) { // TODO: could be a single listener instance for all of them since it uses the param animator.addListener(new AnimatorListenerAdapter() { @@ -427,6 +516,7 @@ public abstract class Transition implements Cloneable { } @Override public void onAnimationEnd(Animator animation) { + runningAnimators.remove(animation); mCurrentAnimators.remove(animation); } }); @@ -691,11 +781,112 @@ public abstract class Transition implements Cloneable { } /** + * Pauses this transition, sending out calls to {@link + * TransitionListener#onTransitionPause(Transition)} to all listeners + * and pausing all running animators started by this transition. + * + * @hide + */ + public void pause() { + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + anim.pause(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList<TransitionListener> tmpListeners = + (ArrayList<TransitionListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionPause(this); + } + } + mPaused = true; + } + + /** + * Resumes this transition, sending out calls to {@link + * TransitionListener#onTransitionPause(Transition)} to all listeners + * and pausing all running animators started by this transition. + * + * @hide + */ + public void resume() { + if (mPaused) { + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + anim.resume(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList<TransitionListener> tmpListeners = + (ArrayList<TransitionListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionResume(this); + } + } + mPaused = false; + } + } + + /** * Called by TransitionManager to play the transition. This calls * play() to set things up and create all of the animations and then * runAnimations() to actually start the animations. */ void playTransition(ViewGroup sceneRoot) { + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + if (anim != null) { + anim.resume(); + AnimationInfo oldInfo = runningAnimators.get(anim); + if (oldInfo != null) { + boolean cancel = false; + TransitionValues oldValues = oldInfo.values; + View oldView = oldInfo.view; + TransitionValues newValues = mEndValues.viewValues != null ? + mEndValues.viewValues.get(oldView) : null; + if (oldValues == null || newValues == null) { + if (oldValues != null || newValues != null) { + cancel = true; + } + } else { + for (String key : oldValues.values.keySet()) { + Object oldValue = oldValues.values.get(key); + Object newValue = newValues.values.get(key); + if ((oldValue == null && newValue != null) || + (oldValue != null && !oldValue.equals(newValue))) { + cancel = true; + if (DBG) { + Log.d(LOG_TAG, "Transition.play: oldValue != newValue for " + + key + ": old, new = " + oldValue + ", " + newValue); + } + break; + } + } + } + if (cancel) { + if (anim.isRunning() || anim.isStarted()) { + if (DBG) { + Log.d(LOG_TAG, "Canceling anim " + anim); + } + anim.cancel(); + } else { + if (DBG) { + Log.d(LOG_TAG, "removing anim from info list: " + anim); + } + runningAnimators.remove(anim); + } + } + } + } + } + // setup() must be called on entire transition hierarchy and set of views // before calling play() on anything; every transition needs a chance to set up // target views appropriately before transitions begin running @@ -707,7 +898,7 @@ public abstract class Transition implements Cloneable { * This is a utility method used by subclasses to handle standard parts of * setting up and running an Animator: it sets the {@link #getDuration() * duration} and the {@link #getStartDelay() startDelay}, starts the - * animation, and, when the animator ends, calls {@link #endTransition()}. + * animation, and, when the animator ends, calls {@link #end()}. * * @param animator The Animator to be run during this transition. * @@ -716,7 +907,7 @@ public abstract class Transition implements Cloneable { protected void animate(Animator animator) { // TODO: maybe pass auto-end as a boolean parameter? if (animator == null) { - endTransition(); + end(); } else { if (getDuration() >= 0) { animator.setDuration(getDuration()); @@ -730,7 +921,7 @@ public abstract class Transition implements Cloneable { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - endTransition(); + end(); animation.removeListener(this); } }); @@ -739,39 +930,14 @@ public abstract class Transition implements Cloneable { } /** - * Subclasses may override to receive notice of when the transition starts. - * This is equivalent to listening for the - * {@link TransitionListener#onTransitionStart(Transition)} callback. - */ - protected void onTransitionStart() { - } - - /** - * Subclasses may override to receive notice of when the transition is - * canceled. This is equivalent to listening for the - * {@link TransitionListener#onTransitionCancel(Transition)} callback. - */ - protected void onTransitionCancel() { - } - - /** - * Subclasses may override to receive notice of when the transition ends. - * This is equivalent to listening for the - * {@link TransitionListener#onTransitionEnd(Transition)} callback. - */ - protected void onTransitionEnd() { - } - - /** * This method is called automatically by the transition and * TransitionGroup classes prior to a Transition subclass starting; * subclasses should not need to call it directly. * * @hide */ - protected void startTransition() { + protected void start() { if (mNumInstances == 0) { - onTransitionStart(); if (mListeners != null && mListeners.size() > 0) { ArrayList<TransitionListener> tmpListeners = (ArrayList<TransitionListener>) mListeners.clone(); @@ -790,15 +956,14 @@ public abstract class Transition implements Cloneable { * a transition did nothing (returned a null Animator from * {@link Transition#play(ViewGroup, TransitionValues, * TransitionValues)}) or because the transition returned a valid - * Animator and endTransition() was called in the onAnimationEnd() + * Animator and end() was called in the onAnimationEnd() * callback of the AnimatorListener. * * @hide */ - protected void endTransition() { + protected void end() { --mNumInstances; if (mNumInstances == 0) { - onTransitionEnd(); if (mListeners != null && mListeners.size() > 0) { ArrayList<TransitionListener> tmpListeners = (ArrayList<TransitionListener>) mListeners.clone(); @@ -828,7 +993,7 @@ public abstract class Transition implements Cloneable { * This method cancels a transition that is currently running. * Implementation TBD. */ - protected void cancelTransition() { + protected void cancel() { // TODO: how does this work with instances? // TODO: this doesn't actually do *anything* yet int numAnimators = mCurrentAnimators.size(); @@ -836,7 +1001,6 @@ public abstract class Transition implements Cloneable { Animator animator = mCurrentAnimators.get(i); animator.cancel(); } - onTransitionCancel(); if (mListeners != null && mListeners.size() > 0) { ArrayList<TransitionListener> tmpListeners = (ArrayList<TransitionListener>) mListeners.clone(); @@ -901,11 +1065,28 @@ public abstract class Transition implements Cloneable { Transition clone = null; try { clone = (Transition) super.clone(); + clone.mAnimators = new ArrayList<Animator>(); } catch (CloneNotSupportedException e) {} return clone; } + /** + * Returns the name of this Transition. This name is used internally to distinguish + * between different transitions to determine when interrupting transitions overlap. + * For example, a Move running on the same target view as another Move should determine + * whether the old transition is animating to different end values and should be + * canceled in favor of the new transition. + * + * <p>By default, a Transition's name is simply the value of {@link Class#getName()}, + * but subclasses are free to override and return something different.</p> + * + * @return The name of this transition. + */ + public String getName() { + return mName; + } + String toString(String indent) { String result = indent + getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + ": "; @@ -943,8 +1124,7 @@ public abstract class Transition implements Cloneable { /** * A transition listener receives notifications from a transition. - * Notifications indicate transition lifecycle events: when the transition - * begins, ends, or is canceled. + * Notifications indicate transition lifecycle events. */ public static interface TransitionListener { /** @@ -957,7 +1137,7 @@ public abstract class Transition implements Cloneable { /** * Notification about the end of the transition. Canceled transitions * will always notify listeners of both the cancellation and end - * events. That is, {@link #onTransitionEnd()} is always called, + * events. That is, {@link #onTransitionEnd(Transition)} is always called, * regardless of whether the transition was canceled or played * through to completion. * @@ -967,10 +1147,38 @@ public abstract class Transition implements Cloneable { /** * Notification about the cancellation of the transition. + * Note that cancel() may be called by a parent {@link TransitionGroup} on + * a child transition which has not yet started. This allows the child + * transition to restore state on target objects which was set at + * {@link #play(android.view.ViewGroup, TransitionValues, TransitionValues) + * play()} time. * * @param transition The transition which was canceled. */ void onTransitionCancel(Transition transition); + + /** + * Notification when a transition is paused. + * Note that play() may be called by a parent {@link TransitionGroup} on + * a child transition which has not yet started. This allows the child + * transition to restore state on target objects which was set at + * {@link #play(android.view.ViewGroup, TransitionValues, TransitionValues) + * play()} time. + * + * @param transition The transition which was paused. + */ + void onTransitionPause(Transition transition); + + /** + * Notification when a transition is resumed. + * Note that resume() may be called by a parent {@link TransitionGroup} on + * a child transition which has not yet started. This allows the child + * transition to restore state which may have changed in an earlier call + * to {@link #onTransitionPause(Transition)}. + * + * @param transition The transition which was resumed. + */ + void onTransitionResume(Transition transition); } /** @@ -991,6 +1199,32 @@ public abstract class Transition implements Cloneable { @Override public void onTransitionCancel(Transition transition) { } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } } + /** + * Holds information about each animator used when a new transition starts + * while other transitions are still running to determine whether a running + * animation should be canceled or a new animation noop'd. The structure holds + * information about the state that an animation is going to, to be compared to + * end state of a new animation. + */ + private static class AnimationInfo { + View view; + String name; + TransitionValues values; + + AnimationInfo(View view, String name, TransitionValues values) { + this.view = view; + this.name = name; + this.values = values; + } + } } diff --git a/core/java/android/view/transition/TransitionGroup.java b/core/java/android/view/transition/TransitionGroup.java index 313e33e..b3bacde 100644 --- a/core/java/android/view/transition/TransitionGroup.java +++ b/core/java/android/view/transition/TransitionGroup.java @@ -164,7 +164,7 @@ public class TransitionGroup extends Transition { @Override public void onTransitionStart(Transition transition) { if (!mTransitionGroup.mStarted) { - mTransitionGroup.startTransition(); + mTransitionGroup.start(); mTransitionGroup.mStarted = true; } } @@ -175,7 +175,7 @@ public class TransitionGroup extends Transition { if (mTransitionGroup.mCurrentListeners == 0) { // All child trans mTransitionGroup.mStarted = false; - mTransitionGroup.endTransition(); + mTransitionGroup.end(); } transition.removeListener(this); } @@ -233,12 +233,32 @@ public class TransitionGroup extends Transition { } } + /** @hide */ @Override - protected void cancelTransition() { - super.cancelTransition(); + public void pause() { + super.pause(); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { - mTransitions.get(i).cancelTransition(); + mTransitions.get(i).pause(); + } + } + + /** @hide */ + @Override + public void resume() { + super.resume(); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).resume(); + } + } + + @Override + protected void cancel() { + super.cancel(); + int numTransitions = mTransitions.size(); + for (int i = 0; i < numTransitions; ++i) { + mTransitions.get(i).cancel(); } } diff --git a/core/java/android/view/transition/TransitionManager.java b/core/java/android/view/transition/TransitionManager.java index 7836268..3cb6f68 100644 --- a/core/java/android/view/transition/TransitionManager.java +++ b/core/java/android/view/transition/TransitionManager.java @@ -18,6 +18,7 @@ package android.view.transition; import android.util.ArrayMap; import android.util.Log; +import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -45,8 +46,8 @@ public class TransitionManager { ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); - static ArrayMap<ViewGroup, Transition> sRunningTransitions = - new ArrayMap<ViewGroup, Transition>(); + private static ThreadLocal<ArrayMap<ViewGroup, ArrayList<Transition>>> sRunningTransitions = + new ThreadLocal<ArrayMap<ViewGroup, ArrayList<Transition>>>(); private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>(); @@ -160,6 +161,16 @@ public class TransitionManager { sceneChangeRunTransition(sceneRoot, transitionClone); } + private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() { + ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions = + sRunningTransitions.get(); + if (runningTransitions == null) { + runningTransitions = new ArrayMap<ViewGroup, ArrayList<Transition>>(); + sRunningTransitions.set(runningTransitions); + } + return runningTransitions; + } + private static void sceneChangeRunTransition(final ViewGroup sceneRoot, final Transition transition) { if (transition != null) { @@ -169,16 +180,31 @@ public class TransitionManager { sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); sPendingTransitions.remove(sceneRoot); // Add to running list, handle end to remove it - sRunningTransitions.put(sceneRoot, transition); + final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions = + getRunningTransitions(); + ArrayList<Transition> currentTransitions = runningTransitions.get(sceneRoot); + if (currentTransitions == null) { + currentTransitions = new ArrayList<Transition>(); + runningTransitions.put(sceneRoot, currentTransitions); + } + currentTransitions.add(transition); transition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { - sRunningTransitions.remove(sceneRoot); + ArrayList<Transition> currentTransitions = + runningTransitions.get(sceneRoot); + currentTransitions.remove(transition); } }); transition.captureValues(sceneRoot, false); transition.playTransition(sceneRoot); - return true; + + // Returning false from onPreDraw() skips the current frame. This is + // necessary to avoid artifacts caused by resetting target views + // to their proper end states for capturing. Waiting until the next + // frame to draw allows these views to have their mid-transition + // values set on them again and avoid artifacts. + return false; } }); } @@ -187,14 +213,16 @@ public class TransitionManager { private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) { // Capture current values - Transition runningTransition = sRunningTransitions.get(sceneRoot); + ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot); - if (transition != null) { - transition.captureValues(sceneRoot, true); + if (runningTransitions != null && runningTransitions.size() > 0) { + for (Transition runningTransition : runningTransitions) { + runningTransition.pause(); + } } - if (runningTransition != null) { - runningTransition.cancelTransition(); + if (transition != null) { + transition.captureValues(sceneRoot, true); } // Notify previous scene that it is being exited diff --git a/core/java/android/view/transition/Visibility.java b/core/java/android/view/transition/Visibility.java index 6d39ab6..96ea044 100644 --- a/core/java/android/view/transition/Visibility.java +++ b/core/java/android/view/transition/Visibility.java @@ -19,6 +19,7 @@ package android.view.transition; import android.animation.Animator; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOverlay; import android.view.ViewParent; /** @@ -38,6 +39,10 @@ public abstract class Visibility extends Transition { private static final String PROPNAME_VISIBILITY = "android:visibility:visibility"; private static final String PROPNAME_PARENT = "android:visibility:parent"; + private static String[] sTransitionProperties = { + PROPNAME_VISIBILITY, + PROPNAME_PARENT, + }; private static class VisibilityInfo { boolean visibilityChange; @@ -52,12 +57,42 @@ public abstract class Visibility extends Transition { private VisibilityInfo mTmpVisibilityInfo = new VisibilityInfo(); @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + @Override protected void captureValues(TransitionValues values, boolean start) { int visibility = values.view.getVisibility(); values.values.put(PROPNAME_VISIBILITY, visibility); values.values.put(PROPNAME_PARENT, values.view.getParent()); } + /** + * Returns whether the view is 'visible' according to the given values + * object. This is determined by testing the same properties in the values + * object that are used to determine whether the object is appearing or + * disappearing in the {@link + * #play(android.view.ViewGroup, TransitionValues, TransitionValues)} + * method. This method can be called by, for example, subclasses that want + * to know whether the object is visible in the same way that Visibility + * determines it for the actual animation. + * + * @param values The TransitionValues object that holds the information by + * which visibility is determined. + * @return True if the view reference by <code>values</code> is visible, + * false otherwise. + */ + public boolean isVisible(TransitionValues values) { + if (values == null) { + return false; + } + int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY); + View parent = (View) values.values.get(PROPNAME_PARENT); + + return visibility == View.VISIBLE && parent != null; + } + private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup view) { if (view == sceneRoot) { @@ -197,5 +232,4 @@ public abstract class Visibility extends Transition { TransitionValues endValues, int endVisibility) { return null; } - } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 0224fbe..03bde70 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -780,7 +780,8 @@ public class WebView extends AbsoluteLayout * {@link #loadUrl(String)} instead. * * @param url the URL of the resource to load - * @param postData the data will be passed to "POST" request + * @param postData the data will be passed to "POST" request, which must be + * be "application/x-www-form-urlencoded" encoded. */ public void postUrl(String url, byte[] postData) { checkThread(); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 07198c7..285e6f2 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -1211,13 +1211,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Enables fast scrolling by letting the user quickly scroll through lists by - * dragging the fast scroll thumb. The adapter attached to the list may want - * to implement {@link SectionIndexer} if it wishes to display alphabet preview and - * jump between sections of the list. + * Specifies whether fast scrolling is enabled or disabled. + * <p> + * When fast scrolling is enabled, the user can quickly scroll through lists + * by dragging the fast scroll thumb. + * <p> + * If the adapter backing this list implements {@link SectionIndexer}, the + * fast scroller will display section header previews as the user scrolls. + * Additionally, the user will be able to quickly jump between sections by + * tapping along the length of the scroll bar. + * * @see SectionIndexer * @see #isFastScrollEnabled() - * @param enabled whether or not to enable fast scrolling + * @param enabled true to enable fast scrolling, false otherwise */ public void setFastScrollEnabled(final boolean enabled) { if (mFastScrollEnabled != enabled) { @@ -1252,13 +1258,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Set whether or not the fast scroller should always be shown in place of the - * standard scrollbars. Fast scrollers shown in this way will not fade out and will - * be a permanent fixture within the list. Best combined with an inset scroll bar style - * that will ensure enough padding. This will enable fast scrolling if it is not + * Set whether or not the fast scroller should always be shown in place of + * the standard scroll bars. This will enable fast scrolling if it is not * already enabled. + * <p> + * Fast scrollers shown in this way will not fade out and will be a + * permanent fixture within the list. This is best combined with an inset + * scroll bar style to ensure the scroll bar does not overlap content. * - * @param alwaysShow true if the fast scroller should always be displayed. + * @param alwaysShow true if the fast scroller should always be displayed, + * false otherwise * @see #setScrollBarStyle(int) * @see #setFastScrollEnabled(boolean) */ @@ -1297,10 +1306,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Returns true if the fast scroller is set to always show on this view rather than - * fade out when not in use. + * Returns true if the fast scroller is set to always show on this view. * - * @return true if the fast scroller will always show. + * @return true if the fast scroller will always show * @see #setFastScrollAlwaysVisible(boolean) */ public boolean isFastScrollAlwaysVisible() { @@ -1316,7 +1324,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Returns the current state of the fast scroll feature. + * Returns true if the fast scroller is enabled. + * * @see #setFastScrollEnabled(boolean) * @return true if fast scroll is enabled, false otherwise */ diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 414c318..2b4e520 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -16,6 +16,9 @@ package android.widget; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.content.Context; import android.database.DataSetObserver; import android.graphics.Rect; @@ -23,6 +26,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.IntProperty; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -31,6 +35,7 @@ import android.view.View.MeasureSpec; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.animation.AccelerateDecelerateInterpolator; import java.util.Locale; @@ -956,6 +961,33 @@ public class ListPopupWindow { } /** + * Receives motion events forwarded from a source view. This is used + * internally to implement support for drag-to-open. + * + * @param src view from which the event was forwarded + * @param srcEvent forwarded motion event in source-local coordinates + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + * @hide + */ + public boolean onForwardedEvent(View src, MotionEvent srcEvent, int activePointerId) { + final DropDownListView dst = mDropDownList; + if (dst == null || !dst.isShown()) { + return false; + } + + // Convert event to local coordinates. + final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent); + src.toGlobalMotionEvent(dstEvent); + dst.toLocalMotionEvent(dstEvent); + + // Forward converted event, then recycle it. + final boolean handled = dst.onForwardedEvent(dstEvent, activePointerId); + dstEvent.recycle(); + return handled; + } + + /** * <p>Builds the popup window's content and returns the height the popup * should have. Returns -1 when the content already exists.</p> * @@ -1130,6 +1162,27 @@ public class ListPopupWindow { */ private static class DropDownListView extends ListView { private static final String TAG = ListPopupWindow.TAG + ".DropDownListView"; + + /** Duration in milliseconds of the drag-to-open click animation. */ + private static final long CLICK_ANIM_DURATION = 150; + + /** Target alpha value for drag-to-open click animation. */ + private static final int CLICK_ANIM_ALPHA = 0x80; + + /** Wrapper around Drawable's <code>alpha</code> property. */ + private static final IntProperty<Drawable> DRAWABLE_ALPHA = + new IntProperty<Drawable>("alpha") { + @Override + public void setValue(Drawable object, int value) { + object.setAlpha(value); + } + + @Override + public Integer get(Drawable object) { + return object.getAlpha(); + } + }; + /* * WARNING: This is a workaround for a touch mode issue. * @@ -1165,6 +1218,12 @@ public class ListPopupWindow { */ private boolean mHijackFocus; + /** Whether to force drawing of the pressed state selector. */ + private boolean mDrawsInPressedState; + + /** Current drag-to-open click animation, if any. */ + private Animator mClickAnimation; + /** * <p>Creates a new list view wrapper.</p> * @@ -1178,6 +1237,119 @@ public class ListPopupWindow { } /** + * Handles forwarded events. + * + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + */ + public boolean onForwardedEvent(MotionEvent event, int activePointerId) { + boolean handledEvent = true; + boolean clearPressedItem = false; + + final int actionMasked = event.getActionMasked(); + switch (actionMasked) { + case MotionEvent.ACTION_CANCEL: + handledEvent = false; + break; + case MotionEvent.ACTION_UP: + handledEvent = false; + // $FALL-THROUGH$ + case MotionEvent.ACTION_MOVE: + final int activeIndex = event.findPointerIndex(activePointerId); + if (activeIndex < 0) { + handledEvent = false; + break; + } + + final int x = (int) event.getX(activeIndex); + final int y = (int) event.getY(activeIndex); + final int position = pointToPosition(x, y); + if (position == INVALID_POSITION) { + clearPressedItem = true; + break; + } + + final View child = getChildAt(position - getFirstVisiblePosition()); + setPressedItem(child, position); + handledEvent = true; + + if (actionMasked == MotionEvent.ACTION_UP) { + clickPressedItem(child, position); + } + break; + } + + // Failure to handle the event cancels forwarding. + if (!handledEvent || clearPressedItem) { + clearPressedItem(); + } + + return handledEvent; + } + + /** + * Starts an alpha animation on the selector. When the animation ends, + * the list performs a click on the item. + */ + private void clickPressedItem(final View child, final int position) { + final long id = getItemIdAtPosition(position); + final Animator anim = ObjectAnimator.ofInt( + mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF); + anim.setDuration(CLICK_ANIM_DURATION); + anim.setInterpolator(new AccelerateDecelerateInterpolator()); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + performItemClick(child, position, id); + } + }); + anim.start(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + } + mClickAnimation = anim; + } + + private void clearPressedItem() { + mDrawsInPressedState = false; + setPressed(false); + updateSelectorState(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + mClickAnimation = null; + } + } + + private void setPressedItem(View child, int position) { + mDrawsInPressedState = true; + + // Ordering is essential. First update the pressed state and layout + // the children. This will ensure the selector actually gets drawn. + setPressed(true); + layoutChildren(); + + // Ensure that keyboard focus starts from the last touched position. + setSelectedPositionInt(position); + positionSelector(position, child); + + // Refresh the drawable state to reflect the new pressed state, + // which will also update the selector state. + refreshDrawableState(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + mClickAnimation = null; + } + } + + @Override + boolean touchModeDrawsInPressedState() { + return mDrawsInPressedState || super.touchModeDrawsInPressedState(); + } + + /** * <p>Avoids jarring scrolling effect by ensuring that list elements * made of a text view fit on a single line.</p> * diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 4a98f66..19cc3c2 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -1425,6 +1425,7 @@ public class NumberPicker extends LinearLayout { @Override protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); removeAllCallbacks(); } diff --git a/core/java/android/widget/SectionIndexer.java b/core/java/android/widget/SectionIndexer.java index a1c71f4..f6333d1 100644 --- a/core/java/android/widget/SectionIndexer.java +++ b/core/java/android/widget/SectionIndexer.java @@ -17,38 +17,62 @@ package android.widget; /** - * Interface that should be implemented on Adapters to enable fast scrolling - * in an {@link AbsListView} between sections of the list. A section is a group of list items - * to jump to that have something in common. For example, they may begin with the - * same letter or they may be songs from the same artist. ExpandableListAdapters that - * consider groups and sections as synonymous should account for collapsed groups and return - * an appropriate section/position. + * Interface that may implemented on {@link Adapter}s to enable fast scrolling + * between sections of an {@link AbsListView}. + * <p> + * A section is a group of list items that have something in common. For + * example, they may begin with the same letter or they may be songs from the + * same artist. + * <p> + * {@link ExpandableListAdapter}s that consider groups and sections as + * synonymous should account for collapsed groups and return an appropriate + * section/position. + * + * @see AbsListView#setFastScrollEnabled(boolean) */ public interface SectionIndexer { /** - * This provides the list view with an array of section objects. In the simplest - * case these are Strings, each containing one letter of the alphabet. - * They could be more complex objects that indicate the grouping for the adapter's - * consumption. The list view will call toString() on the objects to get the - * preview letter to display while scrolling. - * @return the array of objects that indicate the different sections of the list. + * Returns an array of objects representing sections of the list. The + * returned array and its contents should be non-null. + * <p> + * The list view will call toString() on the objects to get the preview text + * to display while scrolling. For example, an adapter may return an array + * of Strings representing letters of the alphabet. Or, it may return an + * array of objects whose toString() methods return their section titles. + * + * @return the array of section objects */ Object[] getSections(); - + /** - * Provides the starting index in the list for a given section. - * @param section the index of the section to jump to. - * @return the starting position of that section. If the section is out of bounds, the - * position must be clipped to fall within the size of the list. + * Given the index of a section within the array of section objects, returns + * the starting position of that section within the adapter. + * <p> + * If the section's starting position is outside of the adapter bounds, the + * position must be clipped to fall within the size of the adapter. + * + * @param sectionIndex the index of the section within the array of section + * objects + * @return the starting position of that section within the adapter, + * constrained to fall within the adapter bounds */ - int getPositionForSection(int section); - + int getPositionForSection(int sectionIndex); + /** - * This is a reverse mapping to fetch the section index for a given position - * in the list. - * @param position the position for which to return the section - * @return the section index. If the position is out of bounds, the section index + * Given a position within the adapter, returns the index of the + * corresponding section within the array of section objects. + * <p> + * If the section index is outside of the section array bounds, the index * must be clipped to fall within the size of the section array. + * <p> + * For example, consider an indexer where the section at array index 0 + * starts at adapter position 100. Calling this method with position 10, + * which is before the first section, must return index 0. + * + * @param position the position within the adapter for which to return the + * corresponding section index + * @return the index of the corresponding section within the array of + * section objects, constrained to fall within the array bounds */ - int getSectionForPosition(int position); + int getSectionForPosition(int position); } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index e33c4d4..1c1d77a 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -22,10 +22,12 @@ import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; +import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; @@ -105,6 +107,9 @@ public class TimePicker extends FrameLayout { private Locale mCurrentLocale; + private boolean mHourWithTwoDigit; + private char mHourFormat; + /** * The callback interface used to indicate the time has been adjusted. */ @@ -164,7 +169,7 @@ public class TimePicker extends FrameLayout { // divider (only for the new widget style) mDivider = (TextView) findViewById(R.id.divider); if (mDivider != null) { - mDivider.setText(R.string.time_picker_separator); + setDividerText(); } // minute @@ -235,6 +240,24 @@ public class TimePicker extends FrameLayout { mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); } + if (isAmPmAtStart()) { + // Move the am/pm view to the beginning + ViewGroup amPmParent = (ViewGroup) findViewById(R.id.timePickerLayout); + amPmParent.removeView(amPmView); + amPmParent.addView(amPmView, 0); + // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme for + // example and not for Holo Theme) + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); + final int startMargin = lp.getMarginStart(); + final int endMargin = lp.getMarginEnd(); + if (startMargin != endMargin) { + lp.setMarginStart(endMargin); + lp.setMarginEnd(startMargin); + } + } + + getHourFormatData(); + // update controls to initial state updateHourControl(); updateMinuteControl(); @@ -259,6 +282,35 @@ public class TimePicker extends FrameLayout { } } + private void getHourFormatData() { + final Locale defaultLocale = Locale.getDefault(); + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, + (mIs24HourView) ? "Hm" : "hm"); + final int lengthPattern = bestDateTimePattern.length(); + mHourWithTwoDigit = false; + char hourFormat = '\0'; + // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save + // the hour format that we found. + for (int i = 0; i < lengthPattern; i++) { + final char c = bestDateTimePattern.charAt(i); + if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { + mHourFormat = c; + if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { + mHourWithTwoDigit = true; + } + break; + } + } + } + + private boolean isAmPmAtStart() { + final Locale defaultLocale = Locale.getDefault(); + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, + "hm" /* skeleton */); + + return bestDateTimePattern.startsWith("a"); + } + @Override public void setEnabled(boolean enabled) { if (mIsEnabled == enabled) { @@ -423,9 +475,11 @@ public class TimePicker extends FrameLayout { if (mIs24HourView == is24HourView) { return; } - mIs24HourView = is24HourView; - // cache the current hour since spinner range changes + // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! int currentHour = getCurrentHour(); + // Order is important here. + mIs24HourView = is24HourView; + getHourFormatData(); updateHourControl(); // set value after spinner range is updated setCurrentHour(currentHour); @@ -458,6 +512,38 @@ public class TimePicker extends FrameLayout { onTimeChanged(); } + /** + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the + * separator as the character which is just after the hour marker in the returned pattern. + */ + private void setDividerText() { + final Locale defaultLocale = Locale.getDefault(); + final String skeleton = (mIs24HourView) ? "Hm" : "hm"; + final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, + skeleton); + final String separatorText; + int hourIndex = bestDateTimePattern.lastIndexOf('H'); + if (hourIndex == -1) { + hourIndex = bestDateTimePattern.lastIndexOf('h'); + } + if (hourIndex == -1) { + // Default case + separatorText = ":"; + } else { + int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1); + if (minuteIndex == -1) { + separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1)); + } else { + separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex); + } + } + mDivider.setText(separatorText); + } + @Override public int getBaseline() { return mHourSpinner.getBaseline(); @@ -500,14 +586,25 @@ public class TimePicker extends FrameLayout { private void updateHourControl() { if (is24HourView()) { - mHourSpinner.setMinValue(0); - mHourSpinner.setMaxValue(23); - mHourSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); + // 'k' means 1-24 hour + if (mHourFormat == 'k') { + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(24); + } else { + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(23); + } } else { - mHourSpinner.setMinValue(1); - mHourSpinner.setMaxValue(12); - mHourSpinner.setFormatter(null); + // 'K' means 0-11 hour + if (mHourFormat == 'K') { + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(11); + } else { + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(12); + } } + mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null); } private void updateMinuteControl() { diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index ff9678c..863d8cc 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -30,9 +30,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.View.MeasureSpec; import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.ImageButton; -import android.widget.ListPopupWindow; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView; @@ -694,32 +692,43 @@ public class ActionMenuPresenter extends BaseMenuPresenter } @Override - public boolean onTouchObserved(View v, MotionEvent ev) { - if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && v.isEnabled() - && !v.pointInView(ev.getX(), ev.getY(), mScaledTouchSlop)) { - mActivePointerId = ev.getPointerId(0); - v.performClick(); - return true; + public boolean onTouchObserved(View src, MotionEvent srcEvent) { + if (!src.isEnabled()) { + return false; } - return false; + // Always start forwarding events when the source view is touched. + mActivePointerId = srcEvent.getPointerId(0); + src.performClick(); + return true; } @Override - public boolean onTouchForwarded(View v, MotionEvent ev) { - if (!v.isEnabled() || mOverflowPopup == null || !mOverflowPopup.isShowing()) { - return false; - } - - if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { - if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) { + public boolean onTouchForwarded(View src, MotionEvent srcEvent) { + final OverflowPopup popup = mOverflowPopup; + if (popup != null && popup.isShowing()) { + final int activePointerId = mActivePointerId; + if (activePointerId != MotionEvent.INVALID_POINTER_ID && src.isEnabled() + && popup.forwardMotionEvent(src, srcEvent, activePointerId)) { + // Handled the motion event, continue forwarding. return true; } - mActivePointerId = MotionEvent.INVALID_POINTER_ID; + final int activePointerIndex = srcEvent.findPointerIndex(activePointerId); + if (activePointerIndex >= 0) { + final float x = srcEvent.getX(activePointerIndex); + final float y = srcEvent.getY(activePointerIndex); + if (src.pointInView(x, y, mScaledTouchSlop)) { + // The user is touching the source view. Cancel + // forwarding, but don't dismiss the popup. + return false; + } + } + + popup.dismiss(); } - mOverflowPopup.dismiss(); + // Cancel forwarding. return false; } } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 945f42b..9b266df 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -27,7 +27,6 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; @@ -48,8 +47,6 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout; - private final int[] mTempLocation = new int[2]; - private final Context mContext; private final LayoutInflater mInflater; private final MenuBuilder mMenu; @@ -162,67 +159,20 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On return mPopup != null && mPopup.isShowing(); } - public boolean forwardMotionEvent(View v, MotionEvent ev, int activePointerId) { + /** + * Forwards motion events from a source view to the popup window. + * + * @param src view from which the event was forwarded + * @param event forwarded motion event in source-local coordinates + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + */ + public boolean forwardMotionEvent(View src, MotionEvent event, int activePointerId) { if (mPopup == null || !mPopup.isShowing()) { return false; } - final AbsListView dstView = mPopup.getListView(); - if (dstView == null || !dstView.isShown()) { - return false; - } - - boolean cancelForwarding = false; - final int actionMasked = ev.getActionMasked(); - switch (actionMasked) { - case MotionEvent.ACTION_CANCEL: - cancelForwarding = true; - break; - case MotionEvent.ACTION_UP: - cancelForwarding = true; - // $FALL-THROUGH$ - case MotionEvent.ACTION_MOVE: - final int activeIndex = ev.findPointerIndex(activePointerId); - if (activeIndex < 0) { - return false; - } - - final int[] location = mTempLocation; - int x = (int) ev.getX(activeIndex); - int y = (int) ev.getY(activeIndex); - - // Convert to global coordinates. - v.getLocationOnScreen(location); - x += location[0]; - y += location[1]; - - // Convert to local coordinates. - dstView.getLocationOnScreen(location); - x -= location[0]; - y -= location[1]; - - final int position = dstView.pointToPosition(x, y); - if (position >= 0) { - final int childCount = dstView.getChildCount(); - final int firstVisiblePosition = dstView.getFirstVisiblePosition(); - final int index = position - firstVisiblePosition; - if (index < childCount) { - final View child = dstView.getChildAt(index); - if (actionMasked == MotionEvent.ACTION_UP) { - // Touch ended, click highlighted item. - final long id = dstView.getItemIdAtPosition(position); - dstView.performItemClick(child, position, id); - } else if (actionMasked == MotionEvent.ACTION_MOVE) { - // TODO: Highlight touched item, activate after - // long-hover. Consider forwarding events as HOVER and - // letting ListView handle this. - } - } - } - break; - } - - return true; + return mPopup.onForwardedEvent(src, event, activePointerId); } @Override diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 1c11a9f1..d5d746a 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -63,7 +63,6 @@ LOCAL_SRC_FILES:= \ android_os_FileUtils.cpp \ android_os_MemoryFile.cpp \ android_os_MessageQueue.cpp \ - android_os_ParcelFileDescriptor.cpp \ android_os_Parcel.cpp \ android_os_SELinux.cpp \ android_os_SystemClock.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6a25c9f..8472705 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -135,7 +135,6 @@ extern int register_android_text_format_Time(JNIEnv* env); extern int register_android_os_Debug(JNIEnv* env); extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_Parcel(JNIEnv* env); -extern int register_android_os_ParcelFileDescriptor(JNIEnv *env); extern int register_android_os_SELinux(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv *env); extern int register_android_os_SystemClock(JNIEnv* env); @@ -1178,7 +1177,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_FileObserver), REG_JNI(register_android_os_FileUtils), REG_JNI(register_android_os_MessageQueue), - REG_JNI(register_android_os_ParcelFileDescriptor), REG_JNI(register_android_os_SELinux), REG_JNI(register_android_os_Trace), REG_JNI(register_android_os_UEventObserver), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index b03d12a..0ea3bf7 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -222,8 +222,12 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, }
}
- SkBitmap bitmap;
+ // ARGB_4444 is a deprecated format, convert automatically to 8888
+ if (config == SkBitmap::kARGB_4444_Config) {
+ config = SkBitmap::kARGB_8888_Config;
+ }
+ SkBitmap bitmap;
bitmap.setConfig(config, width, height);
jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
@@ -232,8 +236,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, }
if (jColors != NULL) {
- GraphicsJNI::SetPixels(env, jColors, offset, stride,
- 0, 0, width, height, bitmap);
+ GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap);
}
return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL, NULL);
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index f768ce8..5418006 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -306,19 +306,23 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName code->internalDataPath = code->internalDataPathObj.string(); env->ReleaseStringUTFChars(internalDataDir, dirStr); - dirStr = env->GetStringUTFChars(externalDataDir, NULL); - code->externalDataPathObj = dirStr; + if (externalDataDir != NULL) { + dirStr = env->GetStringUTFChars(externalDataDir, NULL); + code->externalDataPathObj = dirStr; + env->ReleaseStringUTFChars(externalDataDir, dirStr); + } code->externalDataPath = code->externalDataPathObj.string(); - env->ReleaseStringUTFChars(externalDataDir, dirStr); code->sdkVersion = sdkVersion; code->assetManager = assetManagerForJavaObject(env, jAssetMgr); - dirStr = env->GetStringUTFChars(obbDir, NULL); - code->obbPathObj = dirStr; + if (obbDir != NULL) { + dirStr = env->GetStringUTFChars(obbDir, NULL); + code->obbPathObj = dirStr; + env->ReleaseStringUTFChars(obbDir, dirStr); + } code->obbPath = code->obbPathObj.string(); - env->ReleaseStringUTFChars(obbDir, dirStr); jbyte* rawSavedState = NULL; jsize rawSavedSize = 0; diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp deleted file mode 100644 index 99a2d04..0000000 --- a/core/jni/android_os_ParcelFileDescriptor.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//#define LOG_NDEBUG 0 - -#include "JNIHelp.h" - -#include <fcntl.h> -#include <sys/stat.h> -#include <stdio.h> - -#include <utils/Log.h> - -#include <android_runtime/AndroidRuntime.h> - -namespace android -{ - -static struct parcel_file_descriptor_offsets_t -{ - jfieldID mFileDescriptor; -} gParcelFileDescriptorOffsets; - -static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromFd(JNIEnv* env, - jobject clazz, jint origfd) -{ - int fd = dup(origfd); - if (fd < 0) { - jniThrowException(env, "java/io/IOException", strerror(errno)); - return NULL; - } - return jniCreateFileDescriptor(env, fd); -} - -static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromFdNoDup(JNIEnv* env, - jobject clazz, jint fd) -{ - return jniCreateFileDescriptor(env, fd); -} - -static void android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env, - jobject clazz, jobjectArray outFds) -{ - int fds[2]; - if (pipe(fds) < 0) { - int therr = errno; - jniThrowException(env, "java/io/IOException", strerror(therr)); - return; - } - - for (int i=0; i<2; i++) { - jobject fdObj = jniCreateFileDescriptor(env, fds[i]); - env->SetObjectArrayElement(outFds, i, fdObj); - } -} - -static jint getFd(JNIEnv* env, jobject clazz) -{ - jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor); - if (descriptor == NULL) return -1; - return jniGetFDFromFileDescriptor(env, descriptor); -} - -static jlong android_os_ParcelFileDescriptor_getStatSize(JNIEnv* env, - jobject clazz) -{ - jint fd = getFd(env, clazz); - if (fd < 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "bad file descriptor"); - return -1; - } - - struct stat st; - if (fstat(fd, &st) != 0) { - return -1; - } - - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - return st.st_size; - } - - return -1; -} - -static jlong android_os_ParcelFileDescriptor_seekTo(JNIEnv* env, - jobject clazz, jlong pos) -{ - jint fd = getFd(env, clazz); - if (fd < 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "bad file descriptor"); - return -1; - } - - return lseek(fd, pos, SEEK_SET); -} - -static jlong android_os_ParcelFileDescriptor_getFdNative(JNIEnv* env, jobject clazz) -{ - jint fd = getFd(env, clazz); - if (fd < 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "bad file descriptor"); - return -1; - } - - return fd; -} - -static const JNINativeMethod gParcelFileDescriptorMethods[] = { - {"getFileDescriptorFromFd", "(I)Ljava/io/FileDescriptor;", - (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromFd}, - {"getFileDescriptorFromFdNoDup", "(I)Ljava/io/FileDescriptor;", - (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromFdNoDup}, - {"createPipeNative", "([Ljava/io/FileDescriptor;)V", - (void*)android_os_ParcelFileDescriptor_createPipeNative}, - {"getStatSize", "()J", - (void*)android_os_ParcelFileDescriptor_getStatSize}, - {"seekTo", "(J)J", - (void*)android_os_ParcelFileDescriptor_seekTo}, - {"getFdNative", "()I", - (void*)android_os_ParcelFileDescriptor_getFdNative} -}; - -const char* const kParcelFileDescriptorPathName = "android/os/ParcelFileDescriptor"; - -int register_android_os_ParcelFileDescriptor(JNIEnv* env) -{ - jclass clazz = env->FindClass(kParcelFileDescriptorPathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); - gParcelFileDescriptorOffsets.mFileDescriptor = env->GetFieldID(clazz, "mFileDescriptor", "Ljava/io/FileDescriptor;"); - LOG_FATAL_IF(gParcelFileDescriptorOffsets.mFileDescriptor == NULL, - "Unable to find mFileDescriptor field in android.os.ParcelFileDescriptor"); - - return AndroidRuntime::registerNativeMethods( - env, kParcelFileDescriptorPathName, - gParcelFileDescriptorMethods, NELEM(gParcelFileDescriptorMethods)); -} - -} diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml index a78cd85..4fa94f3 100644 --- a/core/res/res/layout/time_picker.xml +++ b/core/res/res/layout/time_picker.xml @@ -20,6 +20,7 @@ <!-- Layout of time picker--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/timePickerLayout" android:orientation="horizontal" android:layout_gravity="center_horizontal" android:layout_width="wrap_content" diff --git a/core/res/res/layout/time_picker_holo.xml b/core/res/res/layout/time_picker_holo.xml index 7d8900e..c6b7d3a 100644 --- a/core/res/res/layout/time_picker_holo.xml +++ b/core/res/res/layout/time_picker_holo.xml @@ -20,14 +20,19 @@ <!-- Layout of time picker --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/timePickerLayout" android:orientation="horizontal" android:layout_gravity="center_horizontal" android:layout_width="wrap_content" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:paddingStart="8dip" + android:paddingEnd="8dip"> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingStart="8dip" + android:paddingEnd="8dip" android:layoutDirection="ltr"> <!-- hour --> @@ -37,8 +42,6 @@ android:layout_height="wrap_content" android:layout_marginTop="16dip" android:layout_marginBottom="16dip" - android:layout_marginStart="16dip" - android:layout_marginEnd="6dip" android:focusable="true" android:focusableInTouchMode="true" /> @@ -48,6 +51,8 @@ android:id="@+id/divider" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginStart="6dip" + android:layout_marginEnd="6dip" android:layout_gravity="center_vertical" android:importantForAccessibility="no" /> @@ -59,8 +64,6 @@ android:layout_height="wrap_content" android:layout_marginTop="16dip" android:layout_marginBottom="16dip" - android:layout_marginStart="6dip" - android:layout_marginEnd="8dip" android:focusable="true" android:focusableInTouchMode="true" /> @@ -75,7 +78,7 @@ android:layout_marginTop="16dip" android:layout_marginBottom="16dip" android:layout_marginStart="8dip" - android:layout_marginEnd="16dip" + android:layout_marginEnd="8dip" android:focusable="true" android:focusableInTouchMode="true" /> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index a9d4b0a..fbeb5b1 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Verkeerde PIN. Probeer weer oor 1 sekonde."</item> <item quantity="other" msgid="8030607343223287654">"Verkeerde PIN. Probeer weer oor <xliff:g id="COUNT">%d</xliff:g> sekondes."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Sleep rand van skerm om balk te wys"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Sleep van rand van skerm af om stelselbalk te wys"</string> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 8fc907a..aaeb2d6 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -314,7 +314,7 @@ <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"የትግበራ መቀያየርን ተከላከል"</string> <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"ተጠቃሚው ከሌላ መተግበሪያ ከመቀየር ይከላከላል።"</string> <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"የአሁኑ የመተግበሪያ መረጃ ያግኙ"</string> - <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"ያዢው በማያ ገጹ ፊት ላይ ስላለው የአሁኑ መተግበሪያ የግል መረጃ እንዲያመጣ ያስችለዋል።"</string> + <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"ያዢው በአሁኑ መተግበሪያ እና በማያ ገጹ ፊት ላይ ስላሉ መተግበሪያዎች የግል መረጃ እንዲያመጣ ያስችለዋል።"</string> <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"ሁሉንም መተግበሪያ ማስነሻ አሳይ እና ተቆጣጠር"</string> <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"እንቅስቃሴዎችን ስርዓቱ እንዴት እንደሚያስጀምር ለመከታተል እና ለመቆጣጠር ለመተግበሪያው ይፈቅዳሉ፡፡ ተንኮል አዘል መተግበሪያዎች የስርዓቱን ክብረ ገመና ሙሉለሙሉ ሊያጋልጡ ይችላሉ፡፡ ይህ ፍቃድ የሚያስፈልገው ለግንባታ ብቻ ነው፤ ለመደበኛ አጠቃቀም ፈጽሞ አይደለም፡፡"</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"አካታች የተወገደለት ስርጭት ላክ"</string> @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"ትክክል ያልሆነ ፒን። በ1 ሰከንድ ውስጥ እንደገና ይሞክሩ።"</item> <item quantity="other" msgid="8030607343223287654">"ትክክል ያልሆነ ፒን። በ<xliff:g id="COUNT">%d</xliff:g> ሰከንዶች ውስጥ እንደገና ይሞክሩ።"</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"አሞሌውን ለማሳየት የማያ ገጹን ጠርዝ ላይ ያንሸራትቱ"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"አሞሌውን ለማሳየት ከማያ ገጹ ጠርዝ ጀምረው ያንሸራትቱ"</string> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 03cc7b8..172b36e 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"رقم التعريف الشخصي غير صحيح، الرجاء إعادة المحاولة بعد ثانية واحدة."</item> <item quantity="other" msgid="8030607343223287654">"رقم التعريف الشخصي غير صحيح، الرجاء إعادة المحاولة بعد <xliff:g id="COUNT">%d</xliff:g> من الثواني."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"مرر سريعًا لحافة الشاشة لإظهار الشريط"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"مرر سريعًا من حافة الشاشة لإظهار شريط النظام"</string> </resources> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 83a54b6..72d6483 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1653,4 +1653,8 @@ <skip /> <!-- no translation found for restr_pin_countdown:one (4835639969503729874) --> <!-- no translation found for restr_pin_countdown:other (8030607343223287654) --> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 38ea228..febae04 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Неправилен ПИН код. Опитайте отново след 1 секунда."</item> <item quantity="other" msgid="8030607343223287654">"Неправилен ПИН код. Опитайте отново след <xliff:g id="COUNT">%d</xliff:g> секунди."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index dae5009..a5748b4 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -314,7 +314,7 @@ <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"impedir els canvis d\'aplicació"</string> <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Impedeix que l\'usuari canviï a una altra aplicació."</string> <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obtenció d\'informació de l\'aplicació actual"</string> - <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permet que el titular recuperi informació privada sobre l\'aplicació i els serveis actual al primer pla de la pantalla."</string> + <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permet que el titular recuperi informació privada sobre l\'aplicació i els serveis actuals al primer pla de la pantalla."</string> <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"supervisa i controla tots els inicis d\'aplicacions"</string> <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permet que l\'aplicació supervisi i controli com el sistema inicia activitats. Les aplicacions malicioses poden comprometre totalment el sistema. Aquest permís només és necessari per al desenvolupament, mai per a l\'ús normal."</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar difusió d\'eliminació de paquet"</string> @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN incorrecte. Torna-ho a provar d\'aquí a 1 segon."</item> <item quantity="other" msgid="8030607343223287654">"PIN incorrecte. Torna-ho a provar d\'aquí a <xliff:g id="COUNT">%d</xliff:g> segons."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 8e16e3a..55d45da 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Nesprávný kód PIN. Zkuste to znovu za jednu sekundu."</item> <item quantity="other" msgid="8030607343223287654">"Nesprávný kód PIN. Zkuste to znovu za <xliff:g id="COUNT">%d</xliff:g> s."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Panel zobrazíte přejetím kraje obr."</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Systémový panel zobrazíte přejetím přes okraj obrazovky"</string> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9a053b8..f8e6e7c 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -245,9 +245,9 @@ <string name="permdesc_expandStatusBar" msgid="6917549437129401132">"Tillader, at appen kan udvide og skjule statusbjælken."</string> <string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"omdirigere udgående opkald"</string> <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Tillader, at appen kan behandle udgående opkald og ændre det nummer, der skal ringes til. Med denne tilladelse kan appen overvåge, omdirigere eller forhindre udgående opkald."</string> - <string name="permlab_receiveSms" msgid="8673471768947895082">"modtage tekstbeskeder (SMS)"</string> + <string name="permlab_receiveSms" msgid="8673471768947895082">"modtage tekstbeskeder (sms)"</string> <string name="permdesc_receiveSms" msgid="6424387754228766939">"Tillader, at appen kan modtage og behandle sms-beskeder. Det betyder, at appen kan overvåge eller slette de beskeder, der sendes til din enhed, uden at vise dem til dig."</string> - <string name="permlab_receiveMms" msgid="1821317344668257098">"modtage tekstbeskeder (MMS)"</string> + <string name="permlab_receiveMms" msgid="1821317344668257098">"modtage tekstbeskeder (mms)"</string> <string name="permdesc_receiveMms" msgid="533019437263212260">"Tillader, at appen kan modtage og behandle mms-beskeder. Det betyder, at appen kan overvåge eller slette de beskeder, der sendes til din enhed, uden at vise dem til dig."</string> <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"modtage nødudsendelser"</string> <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"Tillader, at appen kan modtage og behandle nødtransmissioner. Denne tilladelse er kun tilgængelig for systemapps."</string> @@ -257,10 +257,10 @@ <string name="permdesc_sendSms" msgid="7094729298204937667">"Tillader, at appen kan sende sms-beskeder. Dette kan resultere i uventede opkrævninger. Skadelige apps kan koste dig penge ved at sende beskeder uden din bekræftelse."</string> <string name="permlab_sendRespondViaMessageRequest" msgid="8713889105305943200">"send hændelser, hvor der skal svares pr. besked"</string> <string name="permdesc_sendRespondViaMessageRequest" msgid="7107648548468778734">"Tillader, at appen kan sende anmodninger til andre apps til beskeder for at håndtere hændelser, hvor der skal svares pr. besked."</string> - <string name="permlab_readSms" msgid="8745086572213270480">"læse dine tekstbeskeder (SMS eller MMS)"</string> + <string name="permlab_readSms" msgid="8745086572213270480">"læse dine tekstbeskeder (sms eller mms)"</string> <string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Tillader, at appen kan læse de sms-beskeder, der er gemt på din tablet eller dit SIM-kort. Med denne tilladelse kan appen læse alle sms-beskeder, uanset indhold eller fortrolighed."</string> <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Tillader, at appen kan læse de sms-beskeder, der er gemt på din telefon eller dit SIM-kort. Med denne tilladelse kan appen læse alle sms-beskeder, uanset indhold eller fortrolighed."</string> - <string name="permlab_writeSms" msgid="3216950472636214774">"redigere dine tekstbeskeder (SMS eller MMS)"</string> + <string name="permlab_writeSms" msgid="3216950472636214774">"redigere dine tekstbeskeder (sms eller mms)"</string> <string name="permdesc_writeSms" product="tablet" msgid="5160413947794501538">"Tillader, at appen kan skrive til sms-beskeder, der er gemt på din tablet eller på SIM-kortet. Ondsindede apps kan slette dine beskeder."</string> <string name="permdesc_writeSms" product="default" msgid="7268668709052328567">"Tillader, at appen kan skrive til sms-beskeder, der er gemt på din telefon eller dit SIM-kort. Ondsindede apps kan slette dine beskeder."</string> <string name="permlab_receiveWapPush" msgid="5991398711936590410">"modtage tekstbeskeder (WAP)"</string> @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Forkert pinkode. Prøv igen om 1 sekund."</item> <item quantity="other" msgid="8030607343223287654">"Forkert pinkode. Prøv igen om <xliff:g id="COUNT">%d</xliff:g> sekunder."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Stryg fra skærmkanten for at se bjælken"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Stryg med fingeren fra skærmens kant for at se systembjælken"</string> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 7e4d274..1c67f7f 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Falsche PIN. In 1 Sek. erneut versuchen."</item> <item quantity="other" msgid="8030607343223287654">"Falsche PIN. In <xliff:g id="COUNT">%d</xliff:g> Sek. erneut versuchen."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Zum Einblenden der Leiste vom Rand weg wischen"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Zum Einblenden der Systemleiste vom Rand weg wischen"</string> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 7c46560..a01ef71 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Λάθος PIN. Προσπαθήστε ξανά σε 1 δευτερόλεπτο."</item> <item quantity="other" msgid="8030607343223287654">"Λάθος PIN. Προσπαθήστε ξανά σε <xliff:g id="COUNT">%d</xliff:g> δευτερόλεπτα."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Σύρετε την άκρη για εμφάν.γραμμής"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Σύρετε από την άκρη της οθόνης για να εμφανίσετε τη γραμμή συστήματος"</string> </resources> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 22281c1..8a3f354 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Incorrect PIN. Try again in 1 second."</item> <item quantity="other" msgid="8030607343223287654">"Incorrect PIN. Try again in <xliff:g id="COUNT">%d</xliff:g> seconds."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Swipe edge of screen to reveal bar"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Swipe from edge of screen to reveal system bar"</string> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 9c1156bc..c7fe85e 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN incorrecto. Reintentar en 1 s"</item> <item quantity="other" msgid="8030607343223287654">"PIN incorrecto. Reintentar en <xliff:g id="COUNT">%d</xliff:g> s"</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index d64a07a..fa38dbc 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"PIN incorrecto. Inténtalo de nuevo dentro de 1 segundo."</item> <item quantity="other" msgid="8030607343223287654">"PIN incorrecto. Inténtalo de nuevo dentro de <xliff:g id="COUNT">%d</xliff:g> segundos."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Deslizar borde para mostrar barra"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Desliza el borde de la pantalla para mostrar la barra del sistema"</string> </resources> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 386ea2d..28e3ea1 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Vale PIN-kood. Proovige 1 s pärast."</item> <item quantity="other" msgid="8030607343223287654">"Vale PIN-kood. Proovige <xliff:g id="COUNT">%d</xliff:g> s pärast."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 136e027..f6fa564 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"پین نادرست است. امتحان در ۱ ثانیه."</item> <item quantity="other" msgid="8030607343223287654">"پین نادرست است. امتحان در <xliff:g id="COUNT">%d</xliff:g> ثانیه."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 098be12..6089da6 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Väärä PIN. Yritä uudelleen yhden sekunnin kuluttua."</item> <item quantity="other" msgid="8030607343223287654">"Väärä PIN. Yritä uudelleen <xliff:g id="COUNT">%d</xliff:g> sekunnin kuluttua."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 9103e58..24a5107 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN erroné. Réessayez dans 1 seconde."</item> <item quantity="other" msgid="8030607343223287654">"PIN erroné. Réessayez dans <xliff:g id="COUNT">%d</xliff:g> secondes."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 07376ef..89cfc70 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"गलत PIN. 1 सेकंड में पुनः प्रयास करें."</item> <item quantity="other" msgid="8030607343223287654">"गलत PIN. <xliff:g id="COUNT">%d</xliff:g> सेकंड में पुनः प्रयास करें."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"बार को प्रदर्शित करने के लिए स्क्रीन के किनारे को स्वाइप करें"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"सिस्टम बार को प्रदर्शित करने के लिए स्क्रीन के किनारे से स्वाइप करें"</string> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 4cea5a6..a1175f0 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN nije točan. Ponovite za 1 s."</item> <item quantity="other" msgid="8030607343223287654">"PIN nije točan. Ponovite za <xliff:g id="COUNT">%d</xliff:g> s."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 5e0fbc2..9bf05c0 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Helytelen PIN kód. Próbálja újra 1 másodperc múlva."</item> <item quantity="other" msgid="8030607343223287654">"Helytelen PIN kód. Próbálja újra <xliff:g id="COUNT">%d</xliff:g> másodperc múlva."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 1df979b..6ff0e95 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -625,7 +625,7 @@ <string name="permlab_sdcardRead" product="default" msgid="8235341515605559677">"akses uji coba ke penyimpanan yang dilindungi"</string> <string name="permdesc_sdcardRead" product="nosdcard" msgid="3642473292348132072">"Memungkinkan aplikasi menguji izin penyimpanan USB yang akan tersedia di perangkat mendatang."</string> <string name="permdesc_sdcardRead" product="default" msgid="5914402684685848828">"Memungkinkan aplikasi menguji izin untuk kartu SD yang akan tersedia pada perangkat yang akan datang."</string> - <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"ubah/hapus konten pympanan USB"</string> + <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"ubah/hapus isi penyimpanan USB"</string> <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"mengubah atau menghapus konten kartu SD Anda"</string> <string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"Mengizinkan apl menulis ke penyimpanan USB."</string> <string name="permdesc_sdcardWrite" product="default" msgid="4337417790936632090">"Memungkinkan apl menulis ke kartu SD."</string> @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN salah. Coba lagi dalam 1 detik."</item> <item quantity="other" msgid="8030607343223287654">"PIN salah. Coba lagi dalam <xliff:g id="COUNT">%d</xliff:g> detik."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 82cb917..dfac858 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"PIN errato. Riprova tra 1 s."</item> <item quantity="other" msgid="8030607343223287654">"PIN errato. Riprova tra <xliff:g id="COUNT">%d</xliff:g> s."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Scorri bordo schermo per visual. barra"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Fai scorrere il dito dal bordo dello schermo per visualizzare la barra di sistema"</string> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index cef252a..418bd60 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"מספר PIN שגוי. נסה שוב בעוד שניה."</item> <item quantity="other" msgid="8030607343223287654">"מספר PIN שגוי. נסה שוב בעוד <xliff:g id="COUNT">%d</xliff:g> שניות."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index de0369c..8d43e5c 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PINが正しくありません。1秒後にもう一度お試しください。"</item> <item quantity="other" msgid="8030607343223287654">"PINが正しくありません。<xliff:g id="COUNT">%d</xliff:g>秒後にもう一度お試しください。"</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 6477258..8ba74f7 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"잘못된 PIN입니다. 1초 뒤에 다시 시도하세요."</item> <item quantity="other" msgid="8030607343223287654">"잘못된 PIN입니다. <xliff:g id="COUNT">%d</xliff:g>초 뒤에 다시 시도하세요."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 1158a26..cfe8a1c 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -314,7 +314,7 @@ <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"neleisti perjungti programų"</string> <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Neleidžiama naudotojui perjungti į kitą programą."</string> <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"gauti esamos programos informaciją"</string> - <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Savininkui leidžiama gauti privačią informaciją apie dabartinę programą ir paslaugas, naudojamas ekrano priekiniame plane."</string> + <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Savininkui leidžiama gauti privačios informacijos apie dabartinę programą ir paslaugas, naudojamas ekrano priekiniame plane."</string> <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"stebėti ir valdyti visų programų paleidimą"</string> <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Leidžiama programai stebėti ir valdyti, kaip sistema paleidžia veiklą. Kenkėjiškos programos gali visiškai pažeisti sistemą. Šis leidimas reikalingas tik kuriant ir jo niekada nereikia naudojant įprastai."</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"siųsti pašalinto paketo perdavimą"</string> @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Netinkamas PIN kodas. Band. po 1 sek."</item> <item quantity="other" msgid="8030607343223287654">"Netinkamas PIN kodas. Band. po <xliff:g id="COUNT">%d</xliff:g> sek."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Perbr. ekr. kr., kad atsir. juost."</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Perbraukite iš ekrano krašto, kad atsirastų sistemos juosta"</string> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 12acf7a..3fdcde2 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"PIN nav pareizs. Mēģiniet pēc 1 s."</item> <item quantity="other" msgid="8030607343223287654">"PIN nav pareizs. Mēģiniet pēc <xliff:g id="COUNT">%d</xliff:g> s."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Velciet no malas, lai atvērtu joslu"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Velciet no ekrāna malas, lai atvērtu sistēmas joslu."</string> </resources> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index bb12579..26e797a 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -654,7 +654,7 @@ <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"gunakan apl konfigurasi yang disediakan oleh pembawa"</string> <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Membenarkan pemegang menggunakan apl konfigurasi yang diberikan oleh pembawa. Tidak sekali-kali diperlukan untuk apl biasa."</string> <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"dengar pemerhatian mengenai keadaan rangkaian"</string> - <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Membenarkan aplikasi mendengar pemerhantian tentang keadaan rangkaian. Tidak sekali-kali diperlukan untuk apl biasa."</string> + <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Membenarkan aplikasi mendengar pemerhatian tentang keadaan rangkaian. Tidak sekali-kali diperlukan untuk apl biasa."</string> <string name="policylab_limitPassword" msgid="4497420728857585791">"Tetapkan peraturan kata laluan"</string> <string name="policydesc_limitPassword" msgid="3252114203919510394">"Mengawal panjang dan aksara yang dibenarkan dalam kata laluan buka kunci skrin."</string> <string name="policylab_watchLogin" msgid="914130646942199503">"Memantau percubaan buka kunci skrin"</string> @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN salah. Cuba lagi dalam masa 1 saat."</item> <item quantity="other" msgid="8030607343223287654">"PIN salah. Cuba lagi dalam masa <xliff:g id="COUNT">%d</xliff:g> saat."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index e55e6db..9b9d58e 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Feil PIN-kode. Prøv på nytt om 1 sekund."</item> <item quantity="other" msgid="8030607343223287654">"Feil PIN-kode. Prøv på nytt om <xliff:g id="COUNT">%d</xliff:g> sekunder."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Sveip på kanten av skjermen for å få frem feltet"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Sveip fra kanten på skjermen for å få frem systemfeltet"</string> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index d48ccb8..c219fde 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Onjuiste pincode. Probeer het over één seconde opnieuw."</item> <item quantity="other" msgid="8030607343223287654">"Onjuiste pincode. Probeer het over <xliff:g id="COUNT">%d</xliff:g> seconden opnieuw."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Veeg vanaf de rand voor de balk"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Veeg vanaf de rand van het scherm om de systeembalk weer te geven"</string> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 297bc53..2d4db29 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Nieprawidłowy PIN. Spróbuj ponownie za 1 s."</item> <item quantity="other" msgid="8030607343223287654">"Nieprawidłowy PIN. Spróbuj ponownie za <xliff:g id="COUNT">%d</xliff:g> s."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 00eb416..b55a374 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN incorreto. Tente novamente em 1 seg."</item> <item quantity="other" msgid="8030607343223287654">"PIN incorreto. Tente novamente em <xliff:g id="COUNT">%d</xliff:g> seg."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index e5285b1..4fd2b05 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN incorreto. Tente novamente em 1 segundo."</item> <item quantity="other" msgid="8030607343223287654">"PIN incorreto. Tente novamente em <xliff:g id="COUNT">%d</xliff:g> segundos."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index 363fb94..7afb03e 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -2592,4 +2592,8 @@ <skip /> <!-- no translation found for restr_pin_countdown:one (4835639969503729874) --> <!-- no translation found for restr_pin_countdown:other (8030607343223287654) --> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 6fbecc8..24d6d21 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -314,7 +314,7 @@ <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"împiedicare comutare între aplicaţii"</string> <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Împiedică trecerea utilizatorului la o altă aplicaţie."</string> <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obținere informații despre aplicația curentă"</string> - <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite titularului să recupereze informații private despre aplicația și serviciile curente în prim-planul ecranului."</string> + <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite titularului să afișeze informații private despre aplicația și serviciile curente în prim-planul ecranului."</string> <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitorizare şi control asupra lansării tuturor aplicaţiilor"</string> <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permite aplicaţiei să monitorizeze şi să controleze modul în care sistemul lansează activităţi. Aplicaţiile rău intenţionate pot să compromită sistemul în întregime. Această permisiune este necesară doar pentru dezvoltare şi niciodată pentru utilizarea normală."</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"trimitere mesaj difuzat privind extragerea din pachet"</string> @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN incorect. Reîncercați în 1 sec."</item> <item quantity="other" msgid="8030607343223287654">"PIN incorect. Reîncercați în <xliff:g id="COUNT">%d</xliff:g> sec."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index add1291..7730768 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Неверный PIN-код. Повторите попытку через 1 сек."</item> <item quantity="other" msgid="8030607343223287654">"Неверный PIN-код. Повторите попытку через <xliff:g id="COUNT">%d</xliff:g> сек."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index c33a7ea..ad333ac 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Nespr. PIN. Skús. o 1 s"</item> <item quantity="other" msgid="8030607343223287654">"Nespr. PIN. Skús. o <xliff:g id="COUNT">%d</xliff:g> s"</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 45cb215..2a68043 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -314,7 +314,7 @@ <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"preprečevanje preklopa programov"</string> <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Uporabniku preprečuje preklop v drug program."</string> <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"pridobivanje podatkov o trenutni aplikaciji"</string> - <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Imetniku dovoli prenos zasebnih podatkov o trenutni aplikaciji in storitvah v ospredju zaslona."</string> + <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Imetniku dovoli pridobivanje zasebnih podatkov o trenutni aplikaciji in storitvah v ospredju zaslona."</string> <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"spremljanje in nadzor vseh zagonov programov"</string> <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Programu omogoča spremljanje in nadziranje načina, kako sistem zažene dejavnosti. Zlonamerni programi lahko v celoti ogrozijo varnost sistema. To dovoljenje je potrebno samo za razvoj, vendar nikoli za običajno uporabo."</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"pošiljanje oddaje brez paketa"</string> @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Napačen PIN. Poskusite znova čez eno sekundo."</item> <item quantity="other" msgid="8030607343223287654">"Napačen PIN. Poskusite znova čez <xliff:g id="COUNT">%d</xliff:g> s."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Povlecite z roba za prikaz vrstice"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Sistemsko vrstico prikažete tako, da povlečete z roba zaslona"</string> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 404590d..f0ad556 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Нетачан PIN. Покушајте опет за 1 сек."</item> <item quantity="other" msgid="8030607343223287654">"Нетачан PIN. Покушајте опет за <xliff:g id="COUNT">%d</xliff:g> сек."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 221ddb6..3806a45 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Fel pinkod. Försök igenom en sekund."</item> <item quantity="other" msgid="8030607343223287654">"Fel pinkod. Försök igenom om <xliff:g id="COUNT">%d</xliff:g> sekunder."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 767bf44..298a651 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -560,8 +560,8 @@ <string name="permdesc_setTime" product="tablet" msgid="1896341438151152881">"Inaruhusu programu kubadilisha wakati wa saa ya kompyuta kibao."</string> <string name="permdesc_setTime" product="default" msgid="1855702730738020">"Inaruhusu programu kubadilisha wakati wa saa ya simu."</string> <string name="permlab_setTimeZone" msgid="2945079801013077340">"weka saa za eneo"</string> - <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"Inaruhusu programu kubadilisha majira ya saa ya kompyuta kibao."</string> - <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"Inaruhusu programu kubadilisha majira ya saa ya simu."</string> + <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"Huruhusu programu kubadilisha saa za eneo katika kompyuta kibao."</string> + <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"Huruhusu programu kubadilisha saa za eneo katika simu."</string> <string name="permlab_accountManagerService" msgid="4829262349691386986">"tenda kama Huduma ya Meneja wa Akaunti"</string> <string name="permdesc_accountManagerService" msgid="1948455552333615954">"Huruhusu programu kupiga simu kwa Wathibitishaji Akaunti."</string> <string name="permlab_getAccounts" msgid="1086795467760122114">"pata akaunti kwenye kifaa"</string> @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"PIN sio sahihi. Jaribu tena baada ya sekunde 1."</item> <item quantity="other" msgid="8030607343223287654">"PIN sio sahihi. Jaribu tena baada ya sekunde <xliff:g id="COUNT">%d</xliff:g>."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Telezesha kidole kutoka ukingo wa skrini ili kuonyesha upau"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Telezesha kidole kutoka ukingo wa skrini ili kuonyesha upau wa mfumo"</string> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 03d0907..d49d7c5 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN ไม่ถูกต้อง โปรดลองอีกครั้งในอีก 1 วินาที"</item> <item quantity="other" msgid="8030607343223287654">"PIN ไม่ถูกต้อง โปรดลองอีกครั้งในอีก <xliff:g id="COUNT">%d</xliff:g> วินาที"</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index e3f9775..c43918c 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Mali ang PIN. Subukang muli pagkalipas ng 1 segundo."</item> <item quantity="other" msgid="8030607343223287654">"Mali ang PIN. Subukang muli pagkalipas ng <xliff:g id="COUNT">%d</xliff:g> (na) segundo."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 925fea7..ab50621 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Yanlış PIN. 1 saniye içinde tekrar deneyin."</item> <item quantity="other" msgid="8030607343223287654">"Yanlış PIN. <xliff:g id="COUNT">%d</xliff:g> saniye içinde tekrar deneyin."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 41ca8c6..de4267f 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"Неправильний PIN. Повторіть через 1 с."</item> <item quantity="other" msgid="8030607343223287654">"Неправильний PIN. Повторіть через <xliff:g id="COUNT">%d</xliff:g> с."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Гортайте від краю, щоб відкрити панель"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Проведіть пальцем від краю екрана, щоб з’явилась навігаційна панель"</string> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index ef96b32..2fa022a 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"Mã PIN không đúng. Hãy thử lại sau 1 giây nữa."</item> <item quantity="other" msgid="8030607343223287654">"Mã PIN không đúng. Hãy thử lại sau <xliff:g id="COUNT">%d</xliff:g> giây nữa."</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index b4af5d0..5f2757e 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN 码错误。请在1秒钟后重试。"</item> <item quantity="other" msgid="8030607343223287654">"PIN 码错误。请在<xliff:g id="COUNT">%d</xliff:g>秒钟后重试。"</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 062e415..f0d4bb7 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -314,7 +314,7 @@ <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"防止切換應用程式"</string> <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"防止使用者切換到其他應用程式。"</string> <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"取得目前的應用程式資訊"</string> - <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"允許應用程式針對目前在螢幕前景運作的應用程式與服務擷取相關私人資訊。"</string> + <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"允許應用程式擷取目前在螢幕前景運作的應用程式和服務的不公開資訊。"</string> <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"監視及控制所有應用程式的啟動程序"</string> <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"允許應用程式監視和控制系統啟動活動的方式。請注意,惡意應用程式可能利用此功能破壞整個系統。這個權限只有開發人員才需要,一般使用者不需使用這個權限。"</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"傳送程式已移除廣播"</string> @@ -1579,4 +1579,8 @@ <item quantity="one" msgid="4835639969503729874">"PIN 不正確,請於 1 秒後再試一次。"</item> <item quantity="other" msgid="8030607343223287654">"PIN 不正確,請於 <xliff:g id="COUNT">%d</xliff:g> 秒後再試一次。"</item> </plurals> + <!-- no translation found for transient_navigation_confirmation (4907844043611123426) --> + <skip /> + <!-- no translation found for transient_navigation_confirmation_long (8061685920508086697) --> + <skip /> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 538299b..015c31d 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1579,4 +1579,6 @@ <item quantity="one" msgid="4835639969503729874">"I-PIN engalungile. Zama futhi esekhondini elingu-1."</item> <item quantity="other" msgid="8030607343223287654">"I-PIN engalungile. Zama futhi emasekhondini angu-<xliff:g id="COUNT">%d</xliff:g>."</item> </plurals> + <string name="transient_navigation_confirmation" msgid="4907844043611123426">"Swayipha kunqenqema lwesikrini ukuze uveze ibha"</string> + <string name="transient_navigation_confirmation_long" msgid="8061685920508086697">"Swayipha kusukela kunqenqema ukuze uveze ibha yesistimu"</string> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 03e9045..50ea08b 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2378,6 +2378,9 @@ <!-- Set to true in all of the configurations for which this input method should be considered an option as the default. --> <attr name="isDefault" format="boolean" /> + <!-- Set to true if this input method supports ways to switch to + a next input method (e.g. a globe key.). --> + <attr name="supportsSwitchingToNextInputMethod" format="boolean" /> </declare-styleable> <!-- This is the subtype of InputMethod. Subtype can describe locales (e.g. en_US, fr_FR...) diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml index b49e7bd..a7288e1 100644 --- a/core/res/res/values/donottranslate.xml +++ b/core/res/res/values/donottranslate.xml @@ -24,8 +24,6 @@ <bool name="lockscreen_isPortrait">true</bool> <!-- @hide DO NOT TRANSLATE. Control aspect ratio of lock pattern --> <string name="lock_pattern_view_aspect">square</string> - <!-- @hide DO NOT TRANSLATE. Separator between the hour and minute elements in a TimePicker widget --> - <string name="time_picker_separator">:</string> <!-- @hide DO NOT TRANSLATE. ICU pattern for "Mon, 14 January" --> <string name="icu_abbrev_wday_month_day_no_year">eeeMMMMd</string> <!-- @hide DO NOT TRANSLATE. date formatting pattern for system ui.--> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 80c9184..f2ec04f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2072,5 +2072,6 @@ <public type="attr" name="isAsciiCapable" /> <public type="attr" name="customRoots" /> <public type="attr" name="autoMirrored" /> + <public type="attr" name="supportsSwitchingToNextInputMethod" /> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 14ae1e6..ca93d1c 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -217,6 +217,7 @@ <java-symbol type="id" name="pin_new_text" /> <java-symbol type="id" name="pin_confirm_text" /> <java-symbol type="id" name="pin_error_message" /> + <java-symbol type="id" name="timePickerLayout" /> <java-symbol type="attr" name="actionModeShareDrawable" /> <java-symbol type="attr" name="alertDialogCenterButtons" /> @@ -773,7 +774,6 @@ <java-symbol type="string" name="time_picker_increment_hour_button" /> <java-symbol type="string" name="time_picker_increment_minute_button" /> <java-symbol type="string" name="time_picker_increment_set_pm_button" /> - <java-symbol type="string" name="time_picker_separator" /> <java-symbol type="string" name="upload_file" /> <java-symbol type="string" name="user_switched" /> <java-symbol type="string" name="volume_alarm" /> diff --git a/core/tests/coretests/src/android/database/MatrixCursorTest.java b/core/tests/coretests/src/android/database/MatrixCursorTest.java index cdab638..fc48c17 100644 --- a/core/tests/coretests/src/android/database/MatrixCursorTest.java +++ b/core/tests/coretests/src/android/database/MatrixCursorTest.java @@ -128,6 +128,56 @@ public class MatrixCursorTest extends TestCase { } catch (IllegalArgumentException e) { /* expected */ } } + public void testRowBuilderOffer() { + MatrixCursor cursor = newMatrixCursor(); + + cursor.newRow() + .offer("float", 4.2f) + .offer("string", "foobar") + .offer("blob", new byte[] {(byte) 0xaa, (byte) 0x55}) + .offer("lolwat", "kittens"); + + cursor.newRow(); + + cursor.newRow() + .offer("string", "zero") + .offer("string", "one") + .offer("string", "two") + .offer("lolwat", "kittens"); + + assertTrue(cursor.moveToFirst()); + assertEquals("foobar", cursor.getString(0)); + assertEquals(null, cursor.getString(1)); + assertEquals(0, cursor.getShort(1)); + assertEquals(0, cursor.getInt(2)); + assertEquals(0, cursor.getLong(3)); + assertEquals(4.2f, cursor.getFloat(4)); + assertEquals(0.0d, cursor.getDouble(5)); + MoreAsserts.assertEquals(new byte[] {(byte) 0xaa, (byte) 0x55}, cursor.getBlob(6)); + + assertTrue(cursor.moveToNext()); + assertEquals(null, cursor.getString(0)); + assertEquals(0, cursor.getShort(1)); + assertEquals(0, cursor.getInt(2)); + assertEquals(0, cursor.getLong(3)); + assertEquals(0.0f, cursor.getFloat(4)); + assertEquals(0.0d, cursor.getDouble(5)); + assertEquals(null, cursor.getBlob(6)); + + assertTrue(cursor.moveToNext()); + assertEquals("two", cursor.getString(0)); + assertEquals(0, cursor.getShort(1)); + assertEquals(0, cursor.getInt(2)); + assertEquals(0, cursor.getLong(3)); + assertEquals(0.0f, cursor.getFloat(4)); + assertEquals(0.0d, cursor.getDouble(5)); + assertEquals(null, cursor.getBlob(6)); + + assertTrue(cursor.isLast()); + assertFalse(cursor.moveToNext()); + assertTrue(cursor.isAfterLast()); + } + static class NonIterableArrayList<T> extends ArrayList<T> { NonIterableArrayList() {} |