diff options
212 files changed, 4229 insertions, 1604 deletions
diff --git a/api/current.txt b/api/current.txt index 3943695..4fb2fb9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1015,6 +1015,7 @@ package android { field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsRtl = 16843695; // 0x10103af + field public static final int supportsSwitchingToNextInputMethod = 16843753; // 0x10103e9 field public static final int supportsUploading = 16843419; // 0x101029b field public static final int switchMinWidth = 16843632; // 0x1010370 field public static final int switchPadding = 16843633; // 0x1010371 @@ -8051,6 +8052,7 @@ package android.database { public class MatrixCursor.RowBuilder { method public android.database.MatrixCursor.RowBuilder add(java.lang.Object); + method public android.database.MatrixCursor.RowBuilder offer(java.lang.String, java.lang.Object); } public class MergeCursor extends android.database.AbstractCursor { @@ -10924,7 +10926,9 @@ package android.hardware.camera2 { public final class CaptureRequest extends android.hardware.camera2.CameraMetadata implements android.os.Parcelable { method public void addTarget(android.view.Surface); + method public java.lang.Object getTag(); method public void removeTarget(android.view.Surface); + method public void setTag(java.lang.Object); field public static final android.hardware.camera2.CameraMetadata.Key BLACK_LEVEL_LOCK; field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_GAINS; field public static final android.hardware.camera2.CameraMetadata.Key COLOR_CORRECTION_MODE; @@ -12598,6 +12602,7 @@ package android.media { ctor public MediaFormat(); method public final boolean containsKey(java.lang.String); method public static final android.media.MediaFormat createAudioFormat(java.lang.String, int, int); + method public static final android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String); method public static final android.media.MediaFormat createVideoFormat(java.lang.String, int, int); method public final java.nio.ByteBuffer getByteBuffer(java.lang.String); method public final float getFloat(java.lang.String); @@ -12620,8 +12625,11 @@ package android.media { field public static final java.lang.String KEY_HEIGHT = "height"; field public static final java.lang.String KEY_IS_ADTS = "is-adts"; field public static final java.lang.String KEY_I_FRAME_INTERVAL = "i-frame-interval"; + field public static final java.lang.String KEY_LANGUAGE = "language"; field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size"; field public static final java.lang.String KEY_MIME = "mime"; + field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; + field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate"; field public static final java.lang.String KEY_WIDTH = "width"; } @@ -18108,8 +18116,14 @@ package android.os { public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable { ctor public ParcelFileDescriptor(android.os.ParcelFileDescriptor); method public static android.os.ParcelFileDescriptor adoptFd(int); + method public boolean canDetectErrors(); + method public void checkError(boolean) throws java.io.IOException; method public void close() throws java.io.IOException; + method public void closeWithError(java.lang.String) throws java.io.IOException; method public static android.os.ParcelFileDescriptor[] createPipe() throws java.io.IOException; + method public static android.os.ParcelFileDescriptor[] createReliablePipe() throws java.io.IOException; + method public static android.os.ParcelFileDescriptor[] createReliableSocketPair() throws java.io.IOException; + method public static android.os.ParcelFileDescriptor[] createSocketPair() throws java.io.IOException; method public int describeContents(); method public int detachFd(); method public static android.os.ParcelFileDescriptor dup(java.io.FileDescriptor) throws java.io.IOException; @@ -18121,6 +18135,7 @@ package android.os { method public java.io.FileDescriptor getFileDescriptor(); method public long getStatSize(); method public static android.os.ParcelFileDescriptor open(java.io.File, int) throws java.io.FileNotFoundException; + method public static android.os.ParcelFileDescriptor open(java.io.File, int, android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException; method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int MODE_APPEND = 33554432; // 0x2000000 @@ -18128,8 +18143,8 @@ package android.os { field public static final int MODE_READ_ONLY = 268435456; // 0x10000000 field public static final int MODE_READ_WRITE = 805306368; // 0x30000000 field public static final int MODE_TRUNCATE = 67108864; // 0x4000000 - field public static final int MODE_WORLD_READABLE = 1; // 0x1 - field public static final int MODE_WORLD_WRITEABLE = 2; // 0x2 + field public static final deprecated int MODE_WORLD_READABLE = 1; // 0x1 + field public static final deprecated int MODE_WORLD_WRITEABLE = 2; // 0x2 field public static final int MODE_WRITE_ONLY = 536870912; // 0x20000000 } @@ -18141,6 +18156,10 @@ package android.os { ctor public ParcelFileDescriptor.AutoCloseOutputStream(android.os.ParcelFileDescriptor); } + public static abstract interface ParcelFileDescriptor.OnCloseListener { + method public abstract void onClose(java.io.IOException, boolean); + } + public class ParcelFormatException extends java.lang.RuntimeException { ctor public ParcelFormatException(); ctor public ParcelFormatException(java.lang.String); @@ -20607,27 +20626,19 @@ package android.provider { method public static android.net.Uri buildRootsUri(java.lang.String); method public static android.net.Uri buildSearchUri(java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildSearchUri(android.net.Uri, java.lang.String); + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); method public static java.lang.String getDocId(android.net.Uri); method public static android.net.Uri[] getOpenDocuments(android.content.Context); method public static java.lang.String getRootId(android.net.Uri); method public static java.lang.String getSearchQuery(android.net.Uri); method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point); + method public static boolean isLocalOnly(android.net.Uri); + method public static void notifyRootsChanged(android.content.Context, java.lang.String); method public static boolean renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); + method public static android.net.Uri setLocalOnly(android.net.Uri); field public static final java.lang.String EXTRA_HAS_MORE = "has_more"; field public static final java.lang.String EXTRA_REQUEST_MORE = "request_more"; field public static final java.lang.String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 - field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 - field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2 - field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 - field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8 - field public static final java.lang.String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc"; - field public static final java.lang.String PARAM_QUERY = "query"; - field public static final java.lang.String ROOT_DOC_ID = "0"; - field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 - field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4 - field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 - field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 } public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns { @@ -20638,6 +20649,18 @@ package android.provider { field public static final java.lang.String SUMMARY = "summary"; } + public static class DocumentsContract.Documents { + field public static final java.lang.String DOC_ID_ROOT = "0"; + field public static final int FLAG_PREFERS_GRID = 64; // 0x40 + field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 + field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 + field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2 + field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 + field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8 + field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20 + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc"; + } + public static abstract interface DocumentsContract.RootColumns { field public static final java.lang.String AVAILABLE_BYTES = "available_bytes"; field public static final java.lang.String ICON = "icon"; @@ -20647,6 +20670,15 @@ package android.provider { field public static final java.lang.String TITLE = "title"; } + public static class DocumentsContract.Roots { + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.cursor.dir/root"; + field public static final java.lang.String MIME_TYPE_ITEM = "vnd.android.cursor.item/root"; + field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 + field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4 + field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 + field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 + } + public final deprecated class LiveFolders implements android.provider.BaseColumns { field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER"; field public static final java.lang.String DESCRIPTION = "description"; @@ -29478,19 +29510,18 @@ package android.view.transition { public abstract class Transition implements java.lang.Cloneable { ctor public Transition(); method public void addListener(android.view.transition.Transition.TransitionListener); - method protected void cancelTransition(); + method protected void cancel(); method protected abstract void captureValues(android.view.transition.TransitionValues, boolean); method public android.view.transition.Transition clone(); method public long getDuration(); method public android.animation.TimeInterpolator getInterpolator(); method public java.util.ArrayList<android.view.transition.Transition.TransitionListener> getListeners(); + method public java.lang.String getName(); method public long getStartDelay(); method public int[] getTargetIds(); method public android.view.View[] getTargets(); + method public java.lang.String[] getTransitionProperties(); method protected android.view.transition.TransitionValues getTransitionValues(android.view.View, boolean); - method protected void onTransitionCancel(); - method protected void onTransitionEnd(); - method protected void onTransitionStart(); method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues); method public void removeListener(android.view.transition.Transition.TransitionListener); method public android.view.transition.Transition setDuration(long); @@ -29503,6 +29534,8 @@ package android.view.transition { public static abstract interface Transition.TransitionListener { method public abstract void onTransitionCancel(android.view.transition.Transition); method public abstract void onTransitionEnd(android.view.transition.Transition); + method public abstract void onTransitionPause(android.view.transition.Transition); + method public abstract void onTransitionResume(android.view.transition.Transition); method public abstract void onTransitionStart(android.view.transition.Transition); } @@ -29549,6 +29582,7 @@ package android.view.transition { method protected android.animation.Animator appear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int); method protected void captureValues(android.view.transition.TransitionValues, boolean); method protected android.animation.Animator disappear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int); + method public boolean isVisible(android.view.transition.TransitionValues); } } 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() {} diff --git a/docs/downloads/design/roboto-1.100141.zip b/docs/downloads/design/roboto-1.100141.zip Binary files differnew file mode 100644 index 0000000..93dfda7 --- /dev/null +++ b/docs/downloads/design/roboto-1.100141.zip diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd index b13ba62..6d9a60d 100644 --- a/docs/html/design/downloads/index.jd +++ b/docs/html/design/downloads/index.jd @@ -102,7 +102,7 @@ requirements of UI and high-resolution screens.</p> <p> <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto ZIP']);" - href="https://github.com/google/roboto/archive/latest-hinted.zip">Roboto</a> + href="{@docRoot}downloads/design/roboto-1.100141.zip">Roboto</a> <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto Specemin Book']);" href="{@docRoot}downloads/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a> </p> diff --git a/docs/html/design/style/typography.jd b/docs/html/design/style/typography.jd index 0d681ab..818af4c 100644 --- a/docs/html/design/style/typography.jd +++ b/docs/html/design/style/typography.jd @@ -12,7 +12,7 @@ page.tags="textview","font" <p> <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto ZIP']);" - href="https://github.com/google/roboto/archive/latest-hinted.zip">Download Roboto</a> + href="{@docRoot}downloads/design/roboto-1.100141.zip">Download Roboto</a> </p> <p>The Android design language relies on traditional typographic tools such as scale, space, rhythm, diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index df966e1..c0b3bfc 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -226,6 +226,8 @@ void Caches::terminate() { patchCache.clear(); + clearGarbage(); + mInitialized = false; } @@ -331,6 +333,11 @@ void Caches::deleteDisplayListDeferred(DisplayList* displayList) { void Caches::flush(FlushMode mode) { FLUSH_LOGD("Flushing caches (mode %d)", mode); + // We must stop tasks before clearing caches + if (mode > kFlushMode_Layers) { + tasks.stop(); + } + switch (mode) { case kFlushMode_Full: textureCache.clear(); @@ -338,13 +345,13 @@ void Caches::flush(FlushMode mode) { dropShadowCache.clear(); gradientCache.clear(); fontRenderer->clear(); + fboCache.clear(); dither.clear(); // fall through case kFlushMode_Moderate: fontRenderer->flush(); textureCache.flush(); pathCache.clear(); - tasks.stop(); // fall through case kFlushMode_Layers: layerCache.clear(); diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index ee748d39..c9162fe 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -146,7 +146,7 @@ public final class LocationRequest implements Parcelable { private long mExpireAt = Long.MAX_VALUE; // no expiry private int mNumUpdates = Integer.MAX_VALUE; // no expiry private float mSmallestDisplacement = 0.0f; // meters - private WorkSource mWorkSource = new WorkSource(); + private WorkSource mWorkSource = null; private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps private String mProvider = LocationManager.FUSED_PROVIDER; // for deprecated APIs that explicitly request a provider @@ -498,7 +498,16 @@ public final class LocationRequest implements Parcelable { return mSmallestDisplacement; } - /** @hide */ + /** + * Sets the WorkSource to use for power blaming of this location request. + * + * <p>No permissions are required to make this call, however the LocationManager + * will throw a SecurityException when requesting location updates if the caller + * doesn't have the {@link android.Manifest.permission#UPDATE_DEVICE_STATS} permission. + * + * @param workSource WorkSource defining power blame for this location request. + * @hide + */ public void setWorkSource(WorkSource workSource) { mWorkSource = workSource; } @@ -508,7 +517,20 @@ public final class LocationRequest implements Parcelable { return mWorkSource; } - /** @hide */ + /** + * Sets whether or not this location request should be hidden from AppOps. + * + * <p>Hiding a location request from AppOps will remove user visibility in the UI as to this + * request's existence. It does not affect power blaming in the Battery page. + * + * <p>No permissions are required to make this call, however the LocationManager + * will throw a SecurityException when requesting location updates if the caller + * doesn't have the {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} permission. + * + * @param hideFromAppOps If true AppOps won't keep track of this location request. + * @see android.app.AppOpsManager + * @hide + */ public void setHideFromAppOps(boolean hideFromAppOps) { mHideFromAppOps = hideFromAppOps; } @@ -564,7 +586,7 @@ public final class LocationRequest implements Parcelable { request.setHideFromAppOps(in.readInt() != 0); String provider = in.readString(); if (provider != null) request.setProvider(provider); - WorkSource workSource = in.readParcelable(WorkSource.class.getClassLoader()); + WorkSource workSource = in.readParcelable(null); if (workSource != null) request.setWorkSource(workSource); return request; } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 14cdbb7..ef02cfd 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2387,6 +2387,35 @@ public class AudioManager { } } + /** + * @hide + * Notifies AudioService that it is connected to an A2DP device that supports absolute volume, + * so that AudioService can send volume change events to the A2DP device, rather than handling + * them. + */ + public void avrcpSupportsAbsoluteVolume(String address, boolean support) { + IAudioService service = getService(); + try { + service.avrcpSupportsAbsoluteVolume(address, support); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in avrcpSupportsAbsoluteVolume", e); + } + } + + /** + * @hide + * Notifies AudioService of the volume set on the A2DP device as a callback, so AudioService + * is able to update the UI. + */ + public void avrcpUpdateVolume(int oldVolume, int volume) { + IAudioService service = getService(); + try { + service.avrcpUpdateVolume(oldVolume, volume); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in avrcpUpdateVolume", e); + } + } + /** * {@hide} */ diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 290866e..470c571 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -460,6 +460,12 @@ public class AudioService extends IAudioService.Stub { private final MediaFocusControl mMediaFocusControl; + // Reference to BluetoothA2dp to query for AbsoluteVolume. + private BluetoothA2dp mA2dp; + private final Object mA2dpAvrcpLock = new Object(); + // If absolute volume is supported in AVRCP device + private boolean mAvrcpAbsVolSupported = false; + /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// @@ -901,6 +907,15 @@ public class AudioService extends IAudioService.Stub { int oldIndex = mStreamStates[streamType].getIndex(device); if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { + // Check if volume update should be send to AVRCP + synchronized (mA2dpAvrcpLock) { + if (mA2dp != null && mAvrcpAbsVolSupported) { + mA2dp.adjustAvrcpAbsoluteVolume(direction); + return; + // No need to send volume update, because we will update the volume with a + // callback from Avrcp. + } + } if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); @@ -998,6 +1013,15 @@ public class AudioService extends IAudioService.Stub { index = rescaleIndex(index * 10, streamType, streamTypeAlias); + synchronized (mA2dpAvrcpLock) { + if (mA2dp != null && mAvrcpAbsVolSupported) { + mA2dp.setAvrcpAbsoluteVolume(index); + return; + // No need to send volume update, because we will update the volume with a + // callback from Avrcp. + } + } + flags &= ~AudioManager.FLAG_FIXED_VOLUME; if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { @@ -2268,21 +2292,23 @@ public class AudioService extends IAudioService.Stub { List<BluetoothDevice> deviceList; switch(profile) { case BluetoothProfile.A2DP: - BluetoothA2dp a2dp = (BluetoothA2dp) proxy; - deviceList = a2dp.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - synchronized (mConnectedDevices) { - int state = a2dp.getConnectionState(btDevice); - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_CONNECTION_STATE, - state, - 0, - btDevice, - delay); + synchronized (mA2dpAvrcpLock) { + mA2dp = (BluetoothA2dp) proxy; + deviceList = mA2dp.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + synchronized (mConnectedDevices) { + int state = mA2dp.getConnectionState(btDevice); + int delay = checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0); + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_A2DP_CONNECTION_STATE, + state, + 0, + btDevice, + delay); + } } } break; @@ -2344,10 +2370,13 @@ public class AudioService extends IAudioService.Stub { public void onServiceDisconnected(int profile) { switch(profile) { case BluetoothProfile.A2DP: - synchronized (mConnectedDevices) { - if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) { - makeA2dpDeviceUnavailableNow( - mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + synchronized (mA2dpAvrcpLock) { + mA2dp = null; + synchronized (mConnectedDevices) { + if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) { + makeA2dpDeviceUnavailableNow( + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + } } } break; @@ -3697,6 +3726,7 @@ public class AudioService extends IAudioService.Stub { private void onSetA2dpConnectionState(BluetoothDevice btDevice, int state) { + if (DEBUG_VOL) Log.d(TAG, "onSetA2dpConnectionState btDevice="+btDevice+" state="+state); if (btDevice == null) { return; } @@ -3704,6 +3734,20 @@ public class AudioService extends IAudioService.Stub { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } + + // Disable absolute volume, if device is disconnected + synchronized (mA2dpAvrcpLock) { + if (state == BluetoothProfile.STATE_DISCONNECTED && mAvrcpAbsVolSupported) { + mAvrcpAbsVolSupported = false; + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + getDeviceForStream(AudioSystem.STREAM_MUSIC), + 0, + mStreamStates[AudioSystem.STREAM_MUSIC], + 0); + } + } synchronized (mConnectedDevices) { boolean isConnected = (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && @@ -3754,6 +3798,31 @@ public class AudioService extends IAudioService.Stub { } } + public void avrcpSupportsAbsoluteVolume(String address, boolean support) { + // address is not used for now, but may be used when multiple a2dp devices are supported + synchronized (mA2dpAvrcpLock) { + mAvrcpAbsVolSupported = support; + if (support) { + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + int device = getDeviceForStream(AudioSystem.STREAM_MUSIC); + streamState.setIndex(streamState.getMaxIndex(), device); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + } + } + + public void avrcpUpdateVolume(int oldVolume, int volume) { + mStreamStates[AudioSystem.STREAM_MUSIC]. + setIndex(volume, getDeviceForStream(AudioSystem.STREAM_MUSIC)); + sendVolumeUpdate(AudioSystem.STREAM_MUSIC, oldVolume, volume, AudioManager.FLAG_SHOW_UI); + } + private boolean handleDeviceConnection(boolean connected, int device, String params) { synchronized (mConnectedDevices) { boolean isConnected = (mConnectedDevices.containsKey(device) && diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index b4c8a04..903927b 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -98,6 +98,10 @@ interface IAudioService { oneway void reloadAudioSettings(); + oneway void avrcpSupportsAbsoluteVolume(String address, boolean support); + + oneway void avrcpUpdateVolume(int oldVolume, int volume); + void setSpeakerphoneOn(boolean on); boolean isSpeakerphoneOn(); diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 8ddc094..f3356c9 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -20,7 +20,6 @@ import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.view.Surface; import java.lang.ref.WeakReference; @@ -130,11 +129,26 @@ public final class ImageReader implements AutoCloseable { } /** - * <p>Get the next Image from the ImageReader's queue. Returns {@code null} - * if no new image is available.</p> + * <p> + * Get the next Image from the ImageReader's queue. Returns {@code null} if + * no new image is available. + * </p> + * <p> + * This operation will fail by throwing an + * {@link Surface.OutOfResourcesException OutOfResourcesException} if too + * many images have been acquired with {@link #getNextImage}. In particular + * a sequence of {@link #getNextImage} calls greater than {@link #getMaxImages} + * without calling {@link Image#close} or {@link #releaseImage} in-between + * will exhaust the underlying queue. At such a time, + * {@link Surface.OutOfResourcesException OutOfResourcesException} will be + * thrown until more images are released with {@link Image#close} or + * {@link #releaseImage}. + * </p> * * @return a new frame of image data, or {@code null} if no image data is - * available. + * available. + * @throws Surface.OutOfResourcesException if too many images are currently + * acquired */ public Image getNextImage() { SurfaceImage si = new SurfaceImage(); @@ -172,6 +186,8 @@ public final class ImageReader implements AutoCloseable { * @param listener the listener that will be run * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. + * + * @throws IllegalArgumentException if no handler specified and the calling thread has no looper */ public void setImageAvailableListener(OnImageAvailableListener listener, Handler handler) { mImageListener = listener; @@ -260,8 +276,9 @@ public final class ImageReader implements AutoCloseable { * Called from Native code when an Event happens. */ private static void postEventFromNative(Object selfRef) { - WeakReference weakSelf = (WeakReference)selfRef; - final ImageReader ir = (ImageReader)weakSelf.get(); + @SuppressWarnings("unchecked") + WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef; + final ImageReader ir = weakSelf.get(); if (ir == null) { return; } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 3fbaf69..949a42c 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -26,7 +26,7 @@ import java.util.Map; * * The format of the media data is specified as string/value pairs. * - * Keys common to all formats, <b>all keys not marked optional are mandatory</b>: + * Keys common to all audio/video formats, <b>all keys not marked optional are mandatory</b>: * * <table> * <tr><th>Name</th><th>Value Type</th><th>Description</th></tr> @@ -44,6 +44,8 @@ import java.util.Map; * for encoders, readable in the output format of decoders</b></td></tr> * <tr><td>{@link #KEY_FRAME_RATE}</td><td>Integer or Float</td><td><b>encoder-only</b></td></tr> * <tr><td>{@link #KEY_I_FRAME_INTERVAL}</td><td>Integer</td><td><b>encoder-only</b></td></tr> + * <tr><td>{@link #KEY_REPEAT_PREVIOUS_FRAME_AFTER}</td><td>Long</td><td><b>video encoder in surface-mode only</b></td></tr> + * <tr><td>{@link #KEY_PUSH_BLANK_BUFFERS_ON_STOP}</td><td>Integer(1)</td><td><b>video decoder rendering to a surface only</b></td></tr> * </table> * * Audio formats have the following keys: @@ -57,6 +59,11 @@ import java.util.Map; * <tr><td>{@link #KEY_FLAC_COMPRESSION_LEVEL}</td><td>Integer</td><td><b>encoder-only</b>, optional, if content is FLAC audio, specifies the desired compression level.</td></tr> * </table> * + * Subtitle formats have the following keys: + * <table> + * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr> + * <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr> + * </table> */ public final class MediaFormat { private Map<String, Object> mMap; @@ -68,6 +75,12 @@ public final class MediaFormat { public static final String KEY_MIME = "mime"; /** + * A key describing the language of the content. + * The associated value is a string. + */ + public static final String KEY_LANGUAGE = "language"; + + /** * A key describing the sample rate of an audio format. * The associated value is an integer */ @@ -132,6 +145,24 @@ public final class MediaFormat { public static final String KEY_SLICE_HEIGHT = "slice-height"; /** + * Applies only when configuring a video encoder in "surface-input" mode. + * The associated value is a long and gives the time in microseconds + * after which the frame previously submitted to the encoder will be + * repeated (once) if no new frame became available since. + */ + public static final String KEY_REPEAT_PREVIOUS_FRAME_AFTER + = "repeat-previous-frame-after"; + + /** + * If specified when configuring a video decoder rendering to a surface, + * causes the decoder to output "blank", i.e. black frames to the surface + * when stopped to clear out any previously displayed contents. + * The associated value is an integer of value 1. + */ + public static final String KEY_PUSH_BLANK_BUFFERS_ON_STOP + = "push-blank-buffers-on-shutdown"; + + /** * A key describing the duration (in microseconds) of the content. * The associated value is a long. */ @@ -277,6 +308,23 @@ public final class MediaFormat { } /** + * Creates a minimal subtitle format. + * @param mime The mime type of the content. + * @param language The language of the content. Specify "und" if language + * information is only included in the content (similarly, if there + * are multiple language tracks in the content.) + */ + public static final MediaFormat createSubtitleFormat( + String mime, + String language) { + MediaFormat format = new MediaFormat(); + format.setString(KEY_MIME, mime); + format.setString(KEY_LANGUAGE, language); + + return format; + } + + /** * Creates a minimal video format. * @param mime The mime type of the content. * @param width The width of the content (in pixels) diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index cd589de..7d914d2 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -44,6 +44,9 @@ using namespace android; +static const char* const OutOfResourcesException = + "android/view/Surface$OutOfResourcesException"; + enum { IMAGE_READER_MAX_NUM_PLANES = 3, }; @@ -609,7 +612,8 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, nativeFormat = Image_getPixelFormat(env, format); sp<BufferQueue> bq = new BufferQueue(); - sp<CpuConsumer> consumer = new CpuConsumer(bq, true, maxImages); + sp<CpuConsumer> consumer = new CpuConsumer(bq, maxImages, + /*controlledByApp*/true); // TODO: throw dvm exOutOfMemoryError? if (consumer == NULL) { jniThrowRuntimeException(env, "Failed to allocate native CpuConsumer"); @@ -702,7 +706,17 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz, status_t res = consumer->lockNextBuffer(buffer); if (res != NO_ERROR) { if (res != BAD_VALUE /*no buffers*/) { - ALOGE("%s Fail to lockNextBuffer with error: %d ", __FUNCTION__, res); + if (res == NOT_ENOUGH_DATA) { + jniThrowException(env, OutOfResourcesException, + "Too many outstanding images, close existing images" + " to be able to acquire more."); + } else { + ALOGE("%s Fail to lockNextBuffer with error: %d ", + __FUNCTION__, res); + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Unknown error (%d) when we tried to lock buffer.", + res); + } } return false; } @@ -714,6 +728,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz, ALOGE("crop left: %d, top = %d", lt.x, lt.y); jniThrowException(env, "java/lang/UnsupportedOperationException", "crop left top corner need to at origin"); + return false; } // Check if the producer buffer configurations match what ImageReader configured. diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 16a1e48..60142cd 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -753,7 +753,9 @@ static jbyteArray android_media_MediaDrm_provideKeyResponse( status_t err = drm->provideKeyResponse(sessionId, response, keySetId); - throwExceptionAsNecessary(env, err, "Failed to handle key response"); + if (throwExceptionAsNecessary(env, err, "Failed to handle key response")) { + return NULL; + } return VectorToJByteArray(env, keySetId); } @@ -1104,7 +1106,9 @@ static jbyteArray android_media_MediaDrm_encryptNative( status_t err = drm->encrypt(sessionId, keyId, input, iv, output); - throwExceptionAsNecessary(env, err, "Failed to encrypt"); + if (throwExceptionAsNecessary(env, err, "Failed to encrypt")) { + return NULL; + } return VectorToJByteArray(env, output); } @@ -1132,7 +1136,9 @@ static jbyteArray android_media_MediaDrm_decryptNative( Vector<uint8_t> output; status_t err = drm->decrypt(sessionId, keyId, input, iv, output); - throwExceptionAsNecessary(env, err, "Failed to decrypt"); + if (throwExceptionAsNecessary(env, err, "Failed to decrypt")) { + return NULL; + } return VectorToJByteArray(env, output); } @@ -1160,7 +1166,9 @@ static jbyteArray android_media_MediaDrm_signNative( status_t err = drm->sign(sessionId, keyId, message, signature); - throwExceptionAsNecessary(env, err, "Failed to sign"); + if (throwExceptionAsNecessary(env, err, "Failed to sign")) { + return NULL; + } return VectorToJByteArray(env, signature); } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java index 9057f60..624bbaa 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -25,6 +25,8 @@ import android.hardware.IProCameraUser; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.utils.BinderHolder; +import android.hardware.camera2.utils.CameraBinderDecorator; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -109,9 +111,11 @@ public class CameraBinderTest extends AndroidTestCase { String clientPackageName = getContext().getPackageName(); - ICamera cameraUser = mUtils.getCameraService().connect(dummyCallbacks, cameraId, - clientPackageName, - CameraBinderTestUtils.USE_CALLING_UID); + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connect(dummyCallbacks, cameraId, clientPackageName, + CameraBinderTestUtils.USE_CALLING_UID, holder); + ICamera cameraUser = ICamera.Stub.asInterface(holder.getBinder()); assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); Log.v(TAG, String.format("Camera %s connected", cameraId)); @@ -131,9 +135,11 @@ public class CameraBinderTest extends AndroidTestCase { String clientPackageName = getContext().getPackageName(); - IProCameraUser cameraUser = mUtils.getCameraService().connectPro(dummyCallbacks, - cameraId, - clientPackageName, CameraBinderTestUtils.USE_CALLING_UID); + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connectPro(dummyCallbacks, cameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + IProCameraUser cameraUser = IProCameraUser.Stub.asInterface(holder.getBinder()); assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); Log.v(TAG, String.format("Camera %s connected", cameraId)); @@ -161,9 +167,11 @@ public class CameraBinderTest extends AndroidTestCase { String clientPackageName = getContext().getPackageName(); - ICameraDeviceUser cameraUser = mUtils.getCameraService().connectDevice(dummyCallbacks, - cameraId, - clientPackageName, CameraBinderTestUtils.USE_CALLING_UID); + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connectDevice(dummyCallbacks, cameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + ICameraDeviceUser cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); Log.v(TAG, String.format("Camera %s connected", cameraId)); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index bdf14ff..5225e23 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -22,6 +22,7 @@ import android.hardware.camera2.CameraPropertiesKeys; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.utils.BinderHolder; import android.os.RemoteException; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; @@ -39,8 +40,8 @@ public class CameraDeviceBinderTest extends AndroidTestCase { private static String TAG = "CameraDeviceBinderTest"; // Number of streaming callbacks need to check. private static int NUM_CALLBACKS_CHECKED = 10; - // Wait for capture result timeout value: 1000ms - private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1000; + // Wait for capture result timeout value: 1500ms + private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500; private int mCameraId; private ICameraDeviceUser mCameraUser; @@ -129,8 +130,10 @@ public class CameraDeviceBinderTest extends AndroidTestCase { mMockCb = spy(dummyCallbacks); - mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId, - clientPackageName, CameraBinderTestUtils.USE_CALLING_UID); + BinderHolder holder = new BinderHolder(); + mUtils.getCameraService().connectDevice(mMockCb, mCameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + mCameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser); createDefaultSurface(); diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 27f93c0..518dcdc 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -17,13 +17,27 @@ <intent-filter android:priority="100"> <action android:name="android.intent.action.OPEN_DOCUMENT" /> <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.OPENABLE" /> <data android:mimeType="*/*" /> </intent-filter> <intent-filter android:priority="100"> <action android:name="android.intent.action.CREATE_DOCUMENT" /> <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.OPENABLE" /> <data android:mimeType="*/*" /> </intent-filter> + <intent-filter android:priority="100"> + <action android:name="android.intent.action.GET_CONTENT" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.OPENABLE" /> + <data android:mimeType="*/*" /> + </intent-filter> + <!-- data expected to point at existing root to manage --> + <intent-filter> + <action android:name="android.intent.action.MANAGE_DOCUMENT" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="vnd.android.cursor.item/root" /> + </intent-filter> </activity> <activity diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 84f89b4..760f99b 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -47,6 +47,7 @@ <string name="root_type_service">Services</string> <string name="root_type_shortcut">Shortcuts</string> <string name="root_type_device">Devices</string> + <string name="root_type_apps">More apps</string> <string name="pref_advanced_devices">Display advanced devices</string> <string name="pref_file_size">Display file size</string> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index 313774b..575947f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -27,8 +27,8 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.net.Uri; import android.os.Bundle; -import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.Documents; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; @@ -69,7 +69,7 @@ public class CreateDirectoryFragment extends DialogFragment { final String displayName = text1.getText().toString(); final ContentValues values = new ContentValues(); - values.put(DocumentColumns.MIME_TYPE, DocumentsContract.MIME_TYPE_DIRECTORY); + values.put(DocumentColumns.MIME_TYPE, Documents.MIME_TYPE_DIR); values.put(DocumentColumns.DISPLAY_NAME, displayName); final DocumentsActivity activity = (DocumentsActivity) getActivity(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 5a6060a..fbdb3a7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -16,17 +16,24 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.TAG; + import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Loader; +import android.graphics.Bitmap; +import android.graphics.Point; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.provider.DocumentsContract; import android.text.format.DateUtils; import android.text.format.Formatter; +import android.text.format.Time; +import android.util.Log; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.LayoutInflater; @@ -75,6 +82,8 @@ public class DirectoryFragment extends Fragment { private int mType = TYPE_NORMAL; + private Point mThumbSize; + private DocumentsAdapter mAdapter; private LoaderCallbacks<List<Document>> mCallbacks; @@ -144,7 +153,7 @@ public class DirectoryFragment extends Fragment { final DisplayState state = getDisplayState(DirectoryFragment.this); mFilter = new MimePredicate(state.acceptMimes); - final Uri contentsUri; + Uri contentsUri; if (mType == TYPE_NORMAL) { contentsUri = DocumentsContract.buildContentsUri(uri); } else if (mType == TYPE_RECENT_OPEN) { @@ -153,6 +162,10 @@ public class DirectoryFragment extends Fragment { contentsUri = uri; } + if (state.localOnly) { + contentsUri = DocumentsContract.setLocalOnly(contentsUri); + } + final Comparator<Document> sortOrder; if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) { sortOrder = new Document.DateComparator(); @@ -186,10 +199,6 @@ public class DirectoryFragment extends Fragment { @Override public void onStart() { super.onStart(); - - final Context context = getActivity(); - getDisplayState(this).showSize = SettingsActivity.getDisplayFileSize(context); - getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks); } @@ -217,7 +226,9 @@ public class DirectoryFragment extends Fragment { choiceMode = ListView.CHOICE_MODE_NONE; } + final int thumbSize; if (state.mode == DisplayState.MODE_GRID) { + thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width); mListView.setAdapter(null); mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); mGridView.setAdapter(mAdapter); @@ -226,6 +237,7 @@ public class DirectoryFragment extends Fragment { mGridView.setChoiceMode(choiceMode); mCurrentView = mGridView; } else if (state.mode == DisplayState.MODE_LIST) { + thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); mGridView.setAdapter(null); mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); mListView.setAdapter(mAdapter); @@ -234,13 +246,17 @@ public class DirectoryFragment extends Fragment { } else { throw new IllegalStateException(); } + + mThumbSize = new Point(thumbSize, thumbSize); } private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final Document doc = mAdapter.getItem(position); - ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + if (mFilter.apply(doc)) { + ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + } } }; @@ -347,9 +363,21 @@ public class DirectoryFragment extends Fragment { final TextView date = (TextView) convertView.findViewById(R.id.date); final TextView size = (TextView) convertView.findViewById(R.id.size); + final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag(); + if (oldTask != null) { + oldTask.cancel(false); + } + if (doc.isThumbnailSupported()) { - // TODO: load thumbnails async - icon.setImageURI(doc.uri); + final Bitmap cachedResult = ThumbnailCache.get(context).get(doc.uri); + if (cachedResult != null) { + icon.setImageBitmap(cachedResult); + } else { + final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize); + icon.setImageBitmap(null); + icon.setTag(task); + task.execute(doc.uri); + } } else { icon.setImageDrawable(RootsCache.resolveDocumentIcon( context, doc.uri.getAuthority(), doc.mimeType)); @@ -378,14 +406,15 @@ public class DirectoryFragment extends Fragment { (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE); } - // TODO: omit year from format - date.setText(DateUtils.formatSameDayTime( - doc.lastModified, System.currentTimeMillis(), DateFormat.SHORT, - DateFormat.SHORT)); + if (doc.lastModified == -1) { + date.setText(null); + } else { + date.setText(formatTime(context, doc.lastModified)); + } if (state.showSize) { size.setVisibility(View.VISIBLE); - if (doc.isDirectory()) { + if (doc.isDirectory() || doc.size == -1) { size.setText(null); } else { size.setText(Formatter.formatFileSize(context, doc.size)); @@ -411,16 +440,67 @@ public class DirectoryFragment extends Fragment { public long getItemId(int position) { return getItem(position).uri.hashCode(); } + } + + private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> { + private final ImageView mTarget; + private final Point mSize; + + public ThumbnailAsyncTask(ImageView target, Point size) { + mTarget = target; + mSize = size; + } @Override - public boolean areAllItemsEnabled() { - return false; + protected void onPreExecute() { + mTarget.setTag(this); } @Override - public boolean isEnabled(int position) { - final Document doc = getItem(position); - return mFilter.apply(doc); + protected Bitmap doInBackground(Uri... params) { + final Context context = mTarget.getContext(); + final Uri uri = params[0]; + + Bitmap result = null; + try { + result = DocumentsContract.getThumbnail( + context.getContentResolver(), uri, mSize); + if (result != null) { + ThumbnailCache.get(context).put(uri, result); + } + } catch (Exception e) { + Log.w(TAG, "Failed to load thumbnail: " + e); + } + return result; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (mTarget.getTag() == this) { + mTarget.setImageBitmap(result); + mTarget.setTag(null); + } + } + } + + private static String formatTime(Context context, long when) { + // TODO: DateUtils should make this easier + Time then = new Time(); + then.set(when); + Time now = new Time(); + now.setToNow(); + + int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT + | DateUtils.FORMAT_ABBREV_ALL; + + if (then.year != now.year) { + flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE; + } else if (then.yearDay != now.yearDay) { + flags |= DateUtils.FORMAT_SHOW_DATE; + } else { + flags |= DateUtils.FORMAT_SHOW_TIME; } + + return DateUtils.formatDateTime(context, when, flags); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 94c2b61..98f9a4d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -26,6 +26,7 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; +import android.provider.DocumentsContract.DocumentColumns; import android.util.Log; import com.android.documentsui.model.Document; @@ -38,6 +39,7 @@ import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedList; import java.util.List; public class DirectoryLoader extends UriDerivativeLoader<List<Document>> { @@ -46,6 +48,17 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> { private Predicate<Document> mFilter; private Comparator<Document> mSortOrder; + /** + * Stub result that represents an internal error. + */ + public static class ExceptionResult extends LinkedList<Document> { + public final Exception e; + + public ExceptionResult(Exception e) { + this.e = e; + } + } + public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter, Comparator<Document> sortOrder) { super(context, uri); @@ -56,11 +69,18 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> { @Override public List<Document> loadInBackground(Uri uri, CancellationSignal signal) { + try { + return loadInBackgroundInternal(uri, signal); + } catch (Exception e) { + return new ExceptionResult(e); + } + } + + private List<Document> loadInBackgroundInternal(Uri uri, CancellationSignal signal) { final ArrayList<Document> result = Lists.newArrayList(); - // TODO: send selection and sorting hints to backend final ContentResolver resolver = getContext().getContentResolver(); - final Cursor cursor = resolver.query(uri, null, null, null, null, signal); + final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal); try { while (cursor != null && cursor.moveToNext()) { Document doc = null; @@ -94,4 +114,16 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> { return result; } + + private String getQuerySortOrder() { + if (mSortOrder instanceof Document.DateComparator) { + return DocumentColumns.LAST_MODIFIED + " DESC"; + } else if (mSortOrder instanceof Document.NameComparator) { + return DocumentColumns.DISPLAY_NAME + " ASC"; + } else if (mSortOrder instanceof Document.SizeComparator) { + return DocumentColumns.SIZE + " DESC"; + } else { + return null; + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index a536acb..11ccc89 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -22,13 +22,16 @@ import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.content.ClipData; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; @@ -60,6 +63,8 @@ public class DocumentsActivity extends Activity { public static final int ACTION_OPEN = 1; public static final int ACTION_CREATE = 2; + public static final int ACTION_GET_CONTENT = 3; + public static final int ACTION_MANAGE = 4; private int mAction; @@ -84,24 +89,28 @@ public class DocumentsActivity extends Activity { final String action = intent.getAction(); if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { mAction = ACTION_OPEN; - mDisplayState.allowMultiple = intent.getBooleanExtra( - Intent.EXTRA_ALLOW_MULTIPLE, false); } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { mAction = ACTION_CREATE; - mDisplayState.allowMultiple = false; + } else if (Intent.ACTION_GET_CONTENT.equals(action)) { + mAction = ACTION_GET_CONTENT; + } else if (Intent.ACTION_MANAGE_DOCUMENT.equals(action)) { + mAction = ACTION_MANAGE; + } + + if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + mDisplayState.allowMultiple = intent.getBooleanExtra( + Intent.EXTRA_ALLOW_MULTIPLE, false); } - if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { + if (mAction == ACTION_MANAGE) { + mDisplayState.acceptMimes = new String[] { "*/*" }; + } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); } else { mDisplayState.acceptMimes = new String[] { intent.getType() }; } - if (MimePredicate.mimeMatches("image/*", mDisplayState.acceptMimes)) { - mDisplayState.mode = DisplayState.MODE_GRID; - } else { - mDisplayState.mode = DisplayState.MODE_LIST; - } + mDisplayState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); setResult(Activity.RESULT_CANCELED); setContentView(R.layout.activity); @@ -112,7 +121,18 @@ public class DocumentsActivity extends Activity { SaveFragment.show(getFragmentManager(), mimeType, title); } - RootsFragment.show(getFragmentManager()); + if (mAction == ACTION_GET_CONTENT) { + final Intent moreApps = new Intent(getIntent()); + moreApps.setComponent(null); + moreApps.setPackage(null); + RootsFragment.show(getFragmentManager(), moreApps); + } else if (mAction == ACTION_OPEN || mAction == ACTION_CREATE) { + RootsFragment.show(getFragmentManager(), null); + } + + if (mAction == ACTION_MANAGE) { + mDisplayState.sortOrder = DisplayState.SORT_ORDER_DATE; + } mRootsContainer = findViewById(R.id.container_roots); @@ -124,26 +144,54 @@ public class DocumentsActivity extends Activity { mDrawerLayout.setDrawerListener(mDrawerListener); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); - mDrawerLayout.openDrawer(mRootsContainer); + if (mAction == ACTION_MANAGE) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - // Restore last stack for calling package - // TODO: move into async loader - final String packageName = getCallingPackage(); - final Cursor cursor = getContentResolver() - .query(RecentsProvider.buildResume(packageName), null, null, null, null); - try { - if (cursor.moveToFirst()) { - final String raw = cursor.getString( - cursor.getColumnIndex(RecentsProvider.COL_PATH)); - mStack = DocumentStack.deserialize(getContentResolver(), raw); + final Uri rootUri = intent.getData(); + final String authority = rootUri.getAuthority(); + final String rootId = DocumentsContract.getRootId(rootUri); + + final Root root = RootsCache.findRoot(this, authority, rootId); + if (root != null) { + onRootPicked(root, true); + } else { + Log.w(TAG, "Failed to find root: " + rootUri); + finish(); + } + + } else { + mDrawerLayout.openDrawer(mRootsContainer); + + // Restore last stack for calling package + // TODO: move into async loader + final String packageName = getCallingPackage(); + final Cursor cursor = getContentResolver() + .query(RecentsProvider.buildResume(packageName), null, null, null, null); + try { + if (cursor.moveToFirst()) { + final String raw = cursor.getString( + cursor.getColumnIndex(RecentsProvider.COL_PATH)); + mStack = DocumentStack.deserialize(getContentResolver(), raw); + } + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to resume", e); + } finally { + cursor.close(); } - } catch (FileNotFoundException e) { - Log.w(TAG, "Failed to resume", e); - } finally { - cursor.close(); + + onCurrentDirectoryChanged(); } + } - onCurrentDirectoryChanged(); + @Override + public void onStart() { + super.onStart(); + + if (mAction == ACTION_MANAGE) { + mDisplayState.showSize = true; + } else { + mDisplayState.showSize = SettingsActivity.getDisplayFileSize(this); + } } private DrawerListener mDrawerListener = new DrawerListener() { @@ -180,18 +228,20 @@ public class DocumentsActivity extends Activity { final ActionBar actionBar = getActionBar(); actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); if (mDrawerLayout.isDrawerOpen(mRootsContainer)) { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); actionBar.setIcon(new ColorDrawable()); - if (mAction == ACTION_OPEN) { + if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { actionBar.setTitle(R.string.title_open); } else if (mAction == ACTION_CREATE) { actionBar.setTitle(R.string.title_save); } + actionBar.setDisplayHomeAsUpEnabled(true); + mDrawerToggle.setDrawerIndicatorEnabled(true); + } else { final Root root = getCurrentRoot(); actionBar.setIcon(root != null ? root.icon : null); @@ -207,8 +257,13 @@ public class DocumentsActivity extends Activity { } if (mStack.size() > 1) { + actionBar.setDisplayHomeAsUpEnabled(true); + mDrawerToggle.setDrawerIndicatorEnabled(false); + } else if (mAction == ACTION_MANAGE) { + actionBar.setDisplayHomeAsUpEnabled(false); mDrawerToggle.setDrawerIndicatorEnabled(false); } else { + actionBar.setDisplayHomeAsUpEnabled(true); mDrawerToggle.setDrawerIndicatorEnabled(true); } } @@ -259,6 +314,7 @@ public class DocumentsActivity extends Activity { final MenuItem search = menu.findItem(R.id.menu_search); final MenuItem grid = menu.findItem(R.id.menu_grid); final MenuItem list = menu.findItem(R.id.menu_list); + final MenuItem settings = menu.findItem(R.id.menu_settings); grid.setVisible(mDisplayState.mode != DisplayState.MODE_GRID); list.setVisible(mDisplayState.mode != DisplayState.MODE_LIST); @@ -283,6 +339,8 @@ public class DocumentsActivity extends Activity { // TODO: close any search in-progress when hiding search.setVisible(searchVisible); + settings.setVisible(mAction != ACTION_MANAGE); + return true; } @@ -484,12 +542,21 @@ public class DocumentsActivity extends Activity { } } + public void onAppPicked(ResolveInfo info) { + final Intent intent = new Intent(getIntent()); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + intent.setComponent(new ComponentName( + info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); + startActivity(intent); + finish(); + } + public void onDocumentPicked(Document doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { mStack.push(doc); onCurrentDirectoryChanged(); - } else if (mAction == ACTION_OPEN) { + } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { // Explicit file picked, return onFinished(doc.uri); } else if (mAction == ACTION_CREATE) { @@ -538,7 +605,7 @@ public class DocumentsActivity extends Activity { values.put(RecentsProvider.COL_PATH, rawStack); resolver.insert(RecentsProvider.buildRecentCreate(), values); - } else if (mAction == ACTION_OPEN) { + } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { // Remember opened items for (Uri uri : uris) { values.clear(); @@ -565,10 +632,13 @@ public class DocumentsActivity extends Activity { intent.setClipData(clipData); } - // TODO: omit WRITE and PERSIST for GET_CONTENT - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); + if (mAction == ACTION_GET_CONTENT) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); + } setResult(Activity.RESULT_OK, intent); finish(); @@ -580,6 +650,7 @@ public class DocumentsActivity extends Activity { public int sortOrder = SORT_ORDER_NAME; public boolean allowMultiple = false; public boolean showSize = false; + public boolean localOnly = false; public static final int MODE_LIST = 0; public static final int MODE_GRID = 1; diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index f945c6a0..a9929de 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -49,7 +49,9 @@ public class MimePredicate implements Predicate<Document> { } public static boolean mimeMatches(String filter, String test) { - if (filter.equals(test)) { + if (test == null) { + return false; + } else if (filter.equals(test)) { return true; } else if ("*/*".equals(filter)) { return true; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 5268c1d..dbcb039 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -129,11 +129,11 @@ public class RecentsProvider extends ContentProvider { switch (sMatcher.match(uri)) { case URI_RECENT_OPEN: { return db.query(TABLE_RECENT_OPEN, projection, - buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, sortOrder); + buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null); } case URI_RECENT_CREATE: { return db.query(TABLE_RECENT_CREATE, projection, - buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, sortOrder); + buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null); } case URI_RESUME: { final String packageName = uri.getPathSegments().get(1); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index b26db3b..acd9396 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -27,6 +27,7 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Documents; import android.util.Log; import android.util.Pair; @@ -95,19 +96,24 @@ public class RootsCache { sProviders.put(info.providerInfo.authority, info); - // TODO: remove deprecated customRoots flag - // TODO: populate roots on background thread, and cache results - final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority); - final Cursor cursor = context.getContentResolver() - .query(uri, null, null, null, null); try { - while (cursor.moveToNext()) { - final Root root = Root.fromCursor(context, info, cursor); - sRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root); - sRootsList.add(root); + // TODO: remove deprecated customRoots flag + // TODO: populate roots on background thread, and cache results + final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority); + final Cursor cursor = context.getContentResolver() + .query(uri, null, null, null, null); + try { + while (cursor.moveToNext()) { + final Root root = Root.fromCursor(context, info, cursor); + sRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root); + sRootsList.add(root); + } + } finally { + cursor.close(); } - } finally { - cursor.close(); + } catch (Exception e) { + Log.w(TAG, "Failed to load some roots from " + info.providerInfo.authority + + ": " + e); } } } @@ -157,7 +163,7 @@ public class RootsCache { } } - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { return context.getResources().getDrawable(R.drawable.ic_dir); } else { final PackageManager pm = context.getPackageManager(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 427ad42..4973e1d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -22,8 +22,11 @@ import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Roots; import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; @@ -41,6 +44,7 @@ import com.android.documentsui.model.Root; import com.android.documentsui.model.Root.RootComparator; import java.util.Collection; +import java.util.List; /** * Display list of known storage backend roots. @@ -50,8 +54,14 @@ public class RootsFragment extends Fragment { private ListView mList; private SectionedRootsAdapter mAdapter; - public static void show(FragmentManager fm) { + private static final String EXTRA_INCLUDE_APPS = "includeApps"; + + public static void show(FragmentManager fm, Intent includeApps) { + final Bundle args = new Bundle(); + args.putParcelable(EXTRA_INCLUDE_APPS, includeApps); + final RootsFragment fragment = new RootsFragment(); + fragment.setArguments(args); final FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.container_roots, fragment); @@ -69,11 +79,11 @@ public class RootsFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_roots, container, false); mList = (ListView) view.findViewById(android.R.id.list); - - mAdapter = new SectionedRootsAdapter(context, RootsCache.getRoots(context)); - mList.setAdapter(mAdapter); mList.setOnItemClickListener(mItemListener); + final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); + mAdapter = new SectionedRootsAdapter(context, RootsCache.getRoots(context), includeApps); + return view; } @@ -82,18 +92,26 @@ public class RootsFragment extends Fragment { super.onStart(); final Context context = getActivity(); - mAdapter.setShowAdvanced(SettingsActivity.getDisplayAdvancedDevices(context)); + mAdapter.updateVisible(SettingsActivity.getDisplayAdvancedDevices(context)); + mList.setAdapter(mAdapter); } private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Root root = (Root) mAdapter.getItem(position); - ((DocumentsActivity) getActivity()).onRootPicked(root, true); + final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); + final Object item = mAdapter.getItem(position); + if (item instanceof Root) { + activity.onRootPicked((Root) item, true); + } else if (item instanceof ResolveInfo) { + activity.onAppPicked((ResolveInfo) item); + } else { + throw new IllegalStateException("Unknown root: " + item); + } } }; - public static class RootsAdapter extends ArrayAdapter<Root> implements SectionAdapter { + private static class RootsAdapter extends ArrayAdapter<Root> implements SectionAdapter { private int mHeaderId; public RootsAdapter(Context context, int headerId) { @@ -119,8 +137,8 @@ public class RootsFragment extends Fragment { // Device summary is always available space final String summaryText; - if ((root.rootType == DocumentsContract.ROOT_TYPE_DEVICE - || root.rootType == DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED) + if ((root.rootType == Roots.ROOT_TYPE_DEVICE + || root.rootType == Roots.ROOT_TYPE_DEVICE_ADVANCED) && root.availableBytes >= 0) { summaryText = context.getString(R.string.root_available_bytes, Formatter.formatFileSize(context, root.availableBytes)); @@ -148,37 +166,94 @@ public class RootsFragment extends Fragment { } } - public static class SectionedRootsAdapter extends SectionedListAdapter { + private static class AppsAdapter extends ArrayAdapter<ResolveInfo> implements SectionAdapter { + public AppsAdapter(Context context) { + super(context, 0); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Context context = parent.getContext(); + final PackageManager pm = context.getPackageManager(); + if (convertView == null) { + convertView = LayoutInflater.from(context) + .inflate(R.layout.item_root, parent, false); + } + + final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); + + final ResolveInfo info = getItem(position); + icon.setImageDrawable(info.loadIcon(pm)); + title.setText(info.loadLabel(pm)); + + // TODO: match existing summary behavior from disambig dialog + summary.setVisibility(View.GONE); + + return convertView; + } + + @Override + public View getHeaderView(View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_root_header, parent, false); + } + + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + title.setText(R.string.root_type_apps); + + return convertView; + } + } + + private static class SectionedRootsAdapter extends SectionedListAdapter { private final RootsAdapter mServices; private final RootsAdapter mShortcuts; private final RootsAdapter mDevices; private final RootsAdapter mDevicesAdvanced; + private final AppsAdapter mApps; - public SectionedRootsAdapter(Context context, Collection<Root> roots) { + public SectionedRootsAdapter(Context context, Collection<Root> roots, Intent includeApps) { mServices = new RootsAdapter(context, R.string.root_type_service); mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut); mDevices = new RootsAdapter(context, R.string.root_type_device); mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device); + mApps = new AppsAdapter(context); for (Root root : roots) { Log.d(TAG, "Found rootType=" + root.rootType); switch (root.rootType) { - case DocumentsContract.ROOT_TYPE_SERVICE: + case Roots.ROOT_TYPE_SERVICE: mServices.add(root); break; - case DocumentsContract.ROOT_TYPE_SHORTCUT: + case Roots.ROOT_TYPE_SHORTCUT: mShortcuts.add(root); break; - case DocumentsContract.ROOT_TYPE_DEVICE: + case Roots.ROOT_TYPE_DEVICE: mDevices.add(root); mDevicesAdvanced.add(root); break; - case DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED: + case Roots.ROOT_TYPE_DEVICE_ADVANCED: mDevicesAdvanced.add(root); break; } } + if (includeApps != null) { + final PackageManager pm = context.getPackageManager(); + final List<ResolveInfo> infos = pm.queryIntentActivities( + includeApps, PackageManager.MATCH_DEFAULT_ONLY); + + // Omit ourselves from the list + for (ResolveInfo info : infos) { + if (!context.getPackageName().equals(info.activityInfo.packageName)) { + mApps.add(info); + } + } + } + final RootComparator comp = new RootComparator(); mServices.sort(comp); mShortcuts.sort(comp); @@ -186,7 +261,7 @@ public class RootsFragment extends Fragment { mDevicesAdvanced.sort(comp); } - public void setShowAdvanced(boolean showAdvanced) { + public void updateVisible(boolean showAdvanced) { clearSections(); if (mServices.getCount() > 0) { addSection(mServices); @@ -199,6 +274,10 @@ public class RootsFragment extends Fragment { if (devices.getCount() > 0) { addSection(devices); } + + if (mApps.getCount() > 0) { + addSection(mApps); + } } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java index aacce65..088e3fa 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SectionedListAdapter.java @@ -18,6 +18,7 @@ package com.android.documentsui; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ListAdapter; @@ -41,6 +42,11 @@ public class SectionedListAdapter extends BaseAdapter { notifyDataSetChanged(); } + /** + * After mutating sections, you <em>must</em> + * {@link AdapterView#setAdapter(android.widget.Adapter)} to correctly + * recount view types. + */ public void addSection(SectionAdapter adapter) { mSections.add(adapter); notifyDataSetChanged(); @@ -117,7 +123,7 @@ public class SectionedListAdapter extends BaseAdapter { if (position == 0) { return false; } else if (position < sectionSize) { - return section.isEnabled(position); + return section.isEnabled(position - 1); } // Otherwise jump into next section diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java index a086a43..f6548e8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java @@ -60,6 +60,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -75,6 +76,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -90,6 +92,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] { "text/plain", "application/msword" }); @@ -107,6 +110,7 @@ public class TestActivity extends Activity { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt"); startActivityForResult(intent, 42); @@ -114,6 +118,22 @@ public class TestActivity extends Activity { }); view.addView(button); + button = new Button(context); + button.setText("GET_CONTENT */*"); + button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + if (multiple.isChecked()) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + startActivityForResult(Intent.createChooser(intent, "Kittens!"), 42); + } + }); + view.addView(button); + mResult = new TextView(context); view.addView(mResult); @@ -131,7 +151,7 @@ public class TestActivity extends Activity { is = getContentResolver().openInputStream(uri); final int length = Streams.readFullyNoClose(is).length; Log.d(TAG, "read length=" + length); - } catch (IOException e) { + } catch (Exception e) { Log.w(TAG, "Failed to read " + uri, e); } finally { IoUtils.closeQuietly(is); diff --git a/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java b/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java new file mode 100644 index 0000000..bc7abeb --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.LruCache; + +public class ThumbnailCache extends LruCache<Uri, Bitmap> { + private static ThumbnailCache sCache; + + public static ThumbnailCache get(Context context) { + if (sCache == null) { + final ActivityManager am = (ActivityManager) context.getSystemService( + Context.ACTIVITY_SERVICE); + final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024; + sCache = new ThumbnailCache(memoryClassBytes / 4); + } + return sCache; + } + + public ThumbnailCache(int maxSizeBytes) { + super(maxSizeBytes); + } + + @Override + protected int sizeOf(Uri key, Bitmap value) { + return value.getByteCount(); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java index 95922b4..cf45394 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java @@ -21,6 +21,7 @@ import android.database.Cursor; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.Documents; import com.android.documentsui.RecentsProvider; @@ -87,7 +88,7 @@ public class Document { final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); final int flags = getCursorInt(cursor, DocumentColumns.FLAGS) - & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL; + & Documents.FLAG_SUPPORTS_THUMBNAIL; final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); final long size = getCursorLong(cursor, DocumentColumns.SIZE); @@ -127,19 +128,19 @@ public class Document { } public boolean isCreateSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; + return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0; } public boolean isSearchSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0; + return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0; } public boolean isThumbnailSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0; + return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0; } public boolean isDirectory() { - return DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType); + return Documents.MIME_TYPE_DIR.equals(mimeType); } private static String getCursorString(Cursor cursor, String columnName) { @@ -147,9 +148,19 @@ public class Document { return (index != -1) ? cursor.getString(index) : null; } + /** + * Missing or null values are returned as -1. + */ private static long getCursorLong(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); - return (index != -1) ? cursor.getLong(index) : 0; + if (index == -1) return -1; + final String value = cursor.getString(index); + if (value == null) return -1; + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return -1; + } } private static int getCursorInt(Cursor cursor, String columnName) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java index 0880731..23d16df 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java @@ -24,7 +24,9 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Documents; import android.provider.DocumentsContract.RootColumns; +import android.provider.DocumentsContract.Roots; import com.android.documentsui.R; @@ -47,7 +49,7 @@ public class Root { final PackageManager pm = context.getPackageManager(); final Root root = new Root(); root.rootId = null; - root.rootType = DocumentsContract.ROOT_TYPE_SHORTCUT; + root.rootType = Roots.ROOT_TYPE_SHORTCUT; root.uri = null; root.icon = context.getResources().getDrawable(R.drawable.ic_dir); root.title = context.getString(R.string.root_recent); @@ -65,7 +67,7 @@ public class Root { root.rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID)); root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE)); root.uri = DocumentsContract.buildDocumentUri( - info.providerInfo.authority, root.rootId, DocumentsContract.ROOT_DOC_ID); + info.providerInfo.authority, root.rootId, Documents.DOC_ID_ROOT); root.icon = info.providerInfo.loadIcon(pm); root.title = info.providerInfo.loadLabel(pm).toString(); root.availableBytes = cursor.getLong(cursor.getColumnIndex(RootColumns.AVAILABLE_BYTES)); diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index afdb6bb..5272166 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -7,7 +7,7 @@ <application android:label="@string/app_label"> <provider android:name=".ExternalStorageProvider" - android:authorities="com.android.externalstorage" + android:authorities="com.android.externalstorage.documents" android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS"> diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 5c12484..b4bf563 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -22,13 +22,15 @@ import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.BaseColumns; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.Documents; import android.provider.DocumentsContract.RootColumns; +import android.provider.DocumentsContract.Roots; import android.util.Log; import android.webkit.MimeTypeMap; @@ -43,7 +45,7 @@ import java.util.LinkedList; public class ExternalStorageProvider extends ContentProvider { private static final String TAG = "ExternalStorage"; - private static final String AUTHORITY = "com.android.externalstorage"; + private static final String AUTHORITY = "com.android.externalstorage.documents"; // TODO: support multiple storage devices @@ -55,6 +57,14 @@ public class ExternalStorageProvider extends ContentProvider { private static final int URI_DOCS_ID_CONTENTS = 4; private static final int URI_DOCS_ID_SEARCH = 5; + static { + sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS); + sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID); + sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID); + sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS); + sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH); + } + private HashMap<String, Root> mRoots = Maps.newHashMap(); private static class Root { @@ -66,20 +76,22 @@ public class ExternalStorageProvider extends ContentProvider { public File path; } - static { - sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS); - sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH); - } + private static final String[] ALL_ROOTS_COLUMNS = new String[] { + RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE, + RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES + }; + + private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] { + DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, + DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS + }; @Override public boolean onCreate() { mRoots.clear(); final Root root = new Root(); - root.rootType = DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED; + root.rootType = Roots.ROOT_TYPE_DEVICE_ADVANCED; root.name = "primary"; root.title = getContext().getString(R.string.root_internal_storage); root.path = Environment.getExternalStorageDirectory(); @@ -91,64 +103,59 @@ public class ExternalStorageProvider extends ContentProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - - // TODO: support custom projections - final String[] rootsProjection = new String[] { - BaseColumns._ID, RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, - RootColumns.TITLE, RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES }; - final String[] docsProjection = new String[] { - BaseColumns._ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, - DocumentColumns.DOC_ID, DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, - DocumentColumns.FLAGS }; - switch (sMatcher.match(uri)) { case URI_ROOTS: { - final MatrixCursor cursor = new MatrixCursor(rootsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_ROOTS_COLUMNS); for (Root root : mRoots.values()) { - includeRoot(cursor, root); + includeRoot(result, root); } - return cursor; + return result; } case URI_ROOTS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final MatrixCursor cursor = new MatrixCursor(rootsProjection); - includeRoot(cursor, root); - return cursor; + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_ROOTS_COLUMNS); + includeRoot(result, root); + return result; } case URI_DOCS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); - final MatrixCursor cursor = new MatrixCursor(docsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_DOCUMENTS_COLUMNS); final File file = docIdToFile(root, docId); - includeFile(cursor, root, file); - return cursor; + includeFile(result, root, file); + return result; } case URI_DOCS_ID_CONTENTS: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); - final MatrixCursor cursor = new MatrixCursor(docsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_DOCUMENTS_COLUMNS); final File parent = docIdToFile(root, docId); for (File file : parent.listFiles()) { - includeFile(cursor, root, file); + includeFile(result, root, file); } - return cursor; + return result; } case URI_DOCS_ID_SEARCH: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); final String query = DocumentsContract.getSearchQuery(uri).toLowerCase(); - final MatrixCursor cursor = new MatrixCursor(docsProjection); + final MatrixCursor result = new MatrixCursor( + projection != null ? projection : ALL_DOCUMENTS_COLUMNS); final File parent = docIdToFile(root, docId); final LinkedList<File> pending = new LinkedList<File>(); pending.add(parent); - while (!pending.isEmpty() && cursor.getCount() < 20) { + while (!pending.isEmpty() && result.getCount() < 20) { final File file = pending.removeFirst(); if (file.isDirectory()) { for (File child : file.listFiles()) { @@ -156,12 +163,12 @@ public class ExternalStorageProvider extends ContentProvider { } } else { if (file.getName().toLowerCase().contains(query)) { - includeFile(cursor, root, file); + includeFile(result, root, file); } } } - return cursor; + return result; } default: { throw new UnsupportedOperationException("Unsupported Uri " + uri); @@ -173,7 +180,7 @@ public class ExternalStorageProvider extends ContentProvider { String rootPath = root.path.getAbsolutePath(); final String path = file.getAbsolutePath(); if (path.equals(rootPath)) { - return DocumentsContract.ROOT_DOC_ID; + return Documents.DOC_ID_ROOT; } if (!rootPath.endsWith("/")) { @@ -187,55 +194,69 @@ public class ExternalStorageProvider extends ContentProvider { } private File docIdToFile(Root root, String docId) { - if (DocumentsContract.ROOT_DOC_ID.equals(docId)) { + if (Documents.DOC_ID_ROOT.equals(docId)) { return root.path; } else { return new File(root.path, docId); } } - private void includeRoot(MatrixCursor cursor, Root root) { - cursor.addRow(new Object[] { - root.name.hashCode(), root.name, root.rootType, root.icon, root.title, root.summary, - root.path.getFreeSpace() }); + private void includeRoot(MatrixCursor result, Root root) { + final RowBuilder row = result.newRow(); + row.offer(RootColumns.ROOT_ID, root.name); + row.offer(RootColumns.ROOT_TYPE, root.rootType); + row.offer(RootColumns.ICON, root.icon); + row.offer(RootColumns.TITLE, root.title); + row.offer(RootColumns.SUMMARY, root.summary); + row.offer(RootColumns.AVAILABLE_BYTES, root.path.getFreeSpace()); } - private void includeFile(MatrixCursor cursor, Root root, File file) { + private void includeFile(MatrixCursor result, Root root, File file) { int flags = 0; if (file.isDirectory()) { - flags |= DocumentsContract.FLAG_SUPPORTS_SEARCH; + flags |= Documents.FLAG_SUPPORTS_SEARCH; } if (file.isDirectory() && file.canWrite()) { - flags |= DocumentsContract.FLAG_SUPPORTS_CREATE; + flags |= Documents.FLAG_SUPPORTS_CREATE; } if (file.canWrite()) { - flags |= DocumentsContract.FLAG_SUPPORTS_RENAME; - flags |= DocumentsContract.FLAG_SUPPORTS_DELETE; + flags |= Documents.FLAG_SUPPORTS_WRITE; + flags |= Documents.FLAG_SUPPORTS_RENAME; + flags |= Documents.FLAG_SUPPORTS_DELETE; } final String mimeType = getTypeForFile(file); if (mimeType.startsWith("image/")) { - flags |= DocumentsContract.FLAG_SUPPORTS_THUMBNAIL; + flags |= Documents.FLAG_SUPPORTS_THUMBNAIL; } final String docId = fileToDocId(root, file); - final long id = docId.hashCode(); - final String displayName; - if (DocumentsContract.ROOT_DOC_ID.equals(docId)) { + if (Documents.DOC_ID_ROOT.equals(docId)) { displayName = root.title; } else { displayName = file.getName(); } - cursor.addRow(new Object[] { - id, displayName, file.length(), docId, mimeType, file.lastModified(), flags }); + final RowBuilder row = result.newRow(); + row.offer(DocumentColumns.DOC_ID, docId); + row.offer(DocumentColumns.DISPLAY_NAME, displayName); + row.offer(DocumentColumns.SIZE, file.length()); + row.offer(DocumentColumns.MIME_TYPE, mimeType); + row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified()); + row.offer(DocumentColumns.FLAGS, flags); } @Override public String getType(Uri uri) { switch (sMatcher.match(uri)) { + case URI_ROOTS: { + return Roots.MIME_TYPE_DIR; + } + case URI_ROOTS_ID: { + return Roots.MIME_TYPE_ITEM; + } case URI_DOCS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); @@ -249,7 +270,7 @@ public class ExternalStorageProvider extends ContentProvider { private String getTypeForFile(File file) { if (file.isDirectory()) { - return DocumentsContract.MIME_TYPE_DIRECTORY; + return Documents.MIME_TYPE_DIR; } else { return getTypeForName(file.getName()); } @@ -299,7 +320,7 @@ public class ExternalStorageProvider extends ContentProvider { values.getAsString(DocumentColumns.DISPLAY_NAME), mimeType); final File file = new File(parent, name); - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { if (!file.mkdir()) { return null; } @@ -359,7 +380,7 @@ public class ExternalStorageProvider extends ContentProvider { } private String validateDisplayName(String displayName, String mimeType) { - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { return displayName; } else { // Try appending meaningful extension if needed diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 9b2c127..2bed730 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"OUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Kennisgewings verskyn hier"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Verkry enige tyd toegang tot hulle deur af te sleep.\nSleep weer af vir stelselkontroles."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Sleep rand van skerm om balk te wys"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Sleep van rand van skerm af om stelselbalk te wys"</string> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 4aa452d..ebbad16 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -164,7 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ተያይዟል"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"ለGPS በመፈለግ ላይ"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"በ GPS የተዘጋጀ ሥፍራ"</string> - <string name="accessibility_location_active" msgid="2427290146138169014">"ገባሪ የአካባቢ ጥያቄዎች"</string> + <string name="accessibility_location_active" msgid="2427290146138169014">"የአካባቢ ጥያቄዎች ነቅተዋል"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"ሁሉንም ማሳወቂያዎች አጽዳ"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"የመተግበሪያ መረጃ"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"ማያ ገጽ በራስ ሰር ይዞራል።"</string> @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"ራስ-ሰር"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"ማሳወቂያዎች እዚህ ላይ ይታያሉ"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"ወደ ታች በማንሸራተት በማንኛውም ጊዜ ይድረሱባቸው።\nSwipe የስርዓት መቆጣጠሪያዎችን ለማምጣት እንደገና ወደ ታች ያንሸራትቱ።"</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"አሞሌውን ለማሳየት የማያ ገጹን ጠርዝ ላይ ያንሸራትቱ"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"አሞሌውን ለማሳየት ከማያ ገጹ ጠርዝ ጀምረው ያንሸራትቱ"</string> </resources> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index f7f5e37..7aac94e 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"تلقائي"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"تظهر الإشعارات هنا"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"يمكنك الدخول إليها في أي وقت بالتمرير السريع إلى أسفل.\nيمكنك التمرير السريع إلى أسفل مرة أخرى للوصول إلى عناصر تحكم النظام."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"مرر سريعًا لحافة الشاشة لإظهار الشريط"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"مرر سريعًا من حافة الشاشة لإظهار شريط النظام"</string> </resources> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index aaca584..76d0580 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -206,8 +206,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"АЎТА"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Апавяшчэнні з\'яўляюцца тут"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Атрымлівайце доступ да іх у любы час, праводзячы пальцам уніз.\nПравядзіце пальцам уніз яшчэ раз, каб атрымаць доступ да сродкаў кіравання сістэмай."</string> - <!-- no translation found for hiding_navigation_confirmation_message (3227814171674734332) --> - <skip /> - <!-- no translation found for hiding_navigation_confirmation_message_long (7854368870786524950) --> - <skip /> </resources> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index b10f5ff..605dd97 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: Има връзка"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Търси се GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Местоположението е зададено от GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Активни заявки за местоположение"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Изчистване на всички известия."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Информация за приложението"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Екранът ще се завърта автоматично."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"АВТ."</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Известията се показват тук"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Осъществявайте достъп до тях по всяко време, като прекарате пръст надолу.\nНаправете го отново за системните контроли."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Прекарайте пръст по ръба на екрана, за да се покаже лентата"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Прекарайте пръст от ръба на екрана, за да се покаже системната лента"</string> </resources> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 0a95005..83e7020 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: connectada"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"S\'està cercant un GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"S\'ha establert la ubicació per GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Sol·licituds d\'ubicació actives"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Esborra totes les notificacions."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informació de l\'aplicació"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"La pantalla girarà automàticament."</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMÀTICA"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Les notificacions apareixen aquí"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Accedeix-hi en qualsevol moment: només has de fer lliscar el dit cap avall.\nTorna a fer lliscar el dit cap avall per fer que es mostrin els controls del sistema."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Fes lliscar el dit per la vora de la pantalla perquè es mostri la barra"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Fes lliscar el dit des de la vora de la pantalla perquè es mostri la barra del sistema"</string> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 178238f..4497735 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -203,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATICKY"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Zde se zobrazují oznámení"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Můžete je kdykoli zobrazit tím, že přejedete prstem dolů.\nPřejedete-li prstem dolů ještě jednou, zobrazí se ovládací prvky systému."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Panel zobrazíte přejetím přes okraj obrazovky"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Systémový panel zobrazíte přejetím přes okraj obrazovky"</string> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 1c772b5..be42612 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi er forbundet"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Søger efter GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Placeringen er angivet ved hjælp af GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Aktive placeringsanmodninger"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Ryd alle meddelelser."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Oplysninger om appen"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Skærmen roterer automatisk."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Underretninger vises her"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Få adgang til dem når som helst ved at stryge ned.\nStryg ned igen for at komme til systemindstillingerne."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Stryg fra skærmkanten for at se bjælken"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Stryg med fingeren fra skærmens kant for at få vist systembjælken"</string> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 86ea82d..052990c 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"WLAN verbunden"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS wird gesucht"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Standort durch GPS festgelegt"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Standortanfragen aktiv"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Alle Benachrichtigungen löschen"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"App-Details"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Bildschirm wird automatisch gedreht."</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Benachrichtigungen erscheinen hier"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Greifen Sie jederzeit auf sie zu, indem Sie nach unten wischen.\nWischen Sie für Systemeinstellungen erneut nach unten."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Zum Einblenden der Leiste vom Rand weg wischen"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Zum Einblenden der Systemleiste vom Display-Rand weg wischen"</string> </resources> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index f965773..d23b8d5 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -203,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"ΑΥΤΟΜΑΤΗ"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Οι ειδοποιήσεις εμφανίζονται εδώ"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Μεταβείτε σε αυτές ανά πάσα στιγμή σύροντας προς τα κάτω.\nΣύρετε ξανά προς τα κάτω για τα στοιχεία ελέγχου συστήματος."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Σύρετε από την άκρη της οθόνης για να εμφανίσετε τη γραμμή"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Σύρετε από την άκρη της οθόνης για να εμφανίσετε τη γραμμή συστήματος"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index a7d6213..2ac1040 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Notifications appear here"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Access them any time by swiping down.\nSwipe down again for system controls."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Swipe edge of screen to reveal bar"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Swipe from edge of screen to reveal system bar"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index bdd0363..d11d413 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Buscando GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"La ubicación se estableció por GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Solicitudes de ubicación activas"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Eliminar todas las notificaciones"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Información de la aplicación"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"La pantalla girará automáticamente."</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMÁTICO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Las notificaciones aparecen aquí."</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Desliza el dedo hacia abajo para acceder al contenido.\nVuelve a deslizar el dedo hacia abajo para acceder a los controles del sistema."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Desliza el dedo desde el borde de la pantalla para mostrar la barra."</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Desliza el dedo desde el borde de la pantalla para mostrar la barra del sistema."</string> </resources> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index a81a2b4..068af45 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Las notificaciones aparecen aquí"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Desliza el dedo hacia abajo para acceder al contenido.\nVuelve a deslizar el dedo hacia abajo para acceder a los controles del sistema."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Desliza el borde de la pantalla para mostrar la barra"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Desliza el borde de la pantalla para mostrar la barra del sistema"</string> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index c4fda13..28ece65 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMAATNE"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Märguanded ilmuvad siia"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Juurdepääs igal ajal sõrmega alla pühkides.\nSüsteemi juhtnuppude jaoks pühkige uuesti alla."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Riba kuvamiseks pühkige ekraani serva"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Süsteemiriba kuvamiseks pühkige ekraani servast"</string> </resources> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index a84b9b4..2ba0427 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"خودکار"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"اعلانها در اینجا نمایش داده میشوند"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"با کشیدن انگشت به طرف پایین به آنها دسترسی پیدا کنید.\nبرای کنترلهای سیستم دوباره انگشت خود را به سمت پایین بکشید."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"برای نمایش نوار، انگشت خود را از لبه صفحه به داخل بکشید"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"برای نمایش نوار سیستم، انگشت خود را از لبه صفحه به داخل بکشید"</string> </resources> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index f2cce06..b652cb0 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Ilmoitukset näkyvät tässä"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Näet ilmoitukset liu\'uttamalla sormea alas ruudulla.\nVoit palauttaa järjestelmän ohjaimet näkyviin liu\'uttamalla sormea alas uudelleen."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Tuo palkki näkyviin liu\'uttamalla ruudun reunasta"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Tuo järjestelmäpalkki näkyviin liu\'uttamalla ruudun reunasta"</string> </resources> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 7fe1143..d00e4a3 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Connecté au Wi-Fi"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Recherche de GPS..."</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Position définie par GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Demandes de localisation actives"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Supprimer toutes les notifications"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informations sur l\'application"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"L\'écran pivote automatiquement."</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATIQUE"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Les notifications s’affichent ici"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Accédez-y à tout moment en faisant glisser le doigt vers le bas.\nRépétez l\'opération pour accéder aux commandes du système."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Faites glisser le doigt sur le côté de l\'écran pour afficher la barre."</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Faites glisser le doigt à partir d\'un côté de l\'écran pour afficher la barre système."</string> </resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index a1de7a1..45b5813 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"स्वत:"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"सूचनाएं यहां दिखाई देती हैं"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"नीचे स्वाइप करके उन तक कभी भी पहुंचें.\nसिस्टम नियंत्रणों के लिए पुन: नीचे स्वाइप करें."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"बार को प्रदर्शित करने के लिए स्क्रीन के किनारे को स्वाइप करें"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"सिस्टम बार को प्रदर्शित करने के लिए स्क्रीन के किनारे से स्वाइप करें"</string> </resources> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index f8ff35d..9f8559c 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATSKI"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Obavijesti se prikazuju ovdje"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Pristupite im u bilo kojem trenutku tako da prstom trznete prema dolje. \nPonovo prstom trznite prema dolje za kontrole sustava."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Prijeđite prstom po rubu zaslona da bi se prikazala traka"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Prijeđite prstom od ruba zaslona da bi se prikazala traka sustava"</string> </resources> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 118baed..f2dcd93 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"automatikus"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Az értesítések itt jelennek meg."</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Bármikor elérheti őket, ha lefelé húzza az ujját.\nHúzza le az ujját még egyszer a rendszerbeállítások eléréséhez."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Csúsztassa ujját a képernyő szélén a sáv megjelenítéséhez"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Csúsztassa ujját a képernyő szélétől a rendszersáv megjelenítéséhez"</string> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 013bd99..6846056 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi tersambung"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Menelusuri GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Lokasi yang disetel oleh GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Permintaan lokasi aktif"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Menghapus semua pemberitahuan."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Info aplikasi"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Layar akan diputar secara otomatis."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"OTOMATIS"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Pemberitahuan muncul di sini"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Akses kapan saja dengan menggesek ke bawah.\nGesek ke bawah sekali lagi untuk kontrol sistem."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Gesek tepi layar untuk membuka bilah"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Gesek dari bagian tepi layar untuk membuka bilah sistem"</string> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index ff539be..a889342 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -203,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Le notifiche vengono visualizzate qui"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Puoi accedervi in qualsiasi momento scorrendo verso il basso.\nFai scorrere di nuovo verso il basso per visualizzare i controlli del sistema."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Fai scorrere il bordo dello schermo per visualizzare la barra"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Fai scorrere il dito dal bordo dello schermo per visualizzare la barra di sistema"</string> </resources> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index c80ad7c..899f092 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi מחובר"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"מחפש GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"מיקום מוגדר על ידי GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"בקשות מיקום פעילות"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"נקה את כל ההתראות."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"פרטי יישום"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"המסך יסתובב באופן אוטומטי."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"אוטומטי"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"הודעות מופיעות כאן"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"גש אליהם בכל עת על ידי החלקה למטה.\nהחלק למטה שוב למעבר למרכז הבקרה של המערכת."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"החלק מקצה המסך כדי להציג את הסרגל"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"החלק מקצה המסך כדי להציג את סרגל המערכת"</string> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 73aa558..e92e8be 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi接続済み"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPSで検索中"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"GPSにより現在地が設定されました"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"現在地リクエストがアクティブ"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"通知をすべて消去。"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"アプリ情報"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"画面は自動的に回転します。"</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"自動"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"ここに通知が表示されます"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"下にスワイプすると、いつでも通知を表示できます。\nシステムを管理するにはもう一度下にスワイプしてください。"</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"バーを表示するには、画面の端からスワイプします"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"システムバーを表示するには、画面の端からスワイプします"</string> </resources> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 403c94b..6319184 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi 연결됨"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS 검색 중"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS에서 위치 설정"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"위치 요청 있음"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"모든 알림 지우기"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"앱 정보"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"화면이 자동으로 회전됩니다."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"자동"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"알림이 여기에 표시됨"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"아래로 스와이프하여 언제든 액세스하세요.\n한 번 더 아래로 스와이프하면 시스템 관리로 이동합니다."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"화면 가장자리에서 스와이프하여 표시줄 표시"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"화면 가장자리에서 스와이프하여 시스템 표시줄 표시"</string> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 11b67ed..4400d37 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Prisij. prie „Wi-Fi“"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Ieškoma GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS nustatyta vieta"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Vietovės užklausos aktyvios"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Išvalyti visus pranešimus."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Programos informacija"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ekranas bus sukamas automatiškai."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATINIS"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Pranešimai rodomi čia"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Perbraukę žemyn bet kuriuo metu pasieksite pranešimus.\nJei norite naudoti sistemos valdiklius, perbraukite žemyn dar kartą."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Jei norite, kad būtų rodoma juosta, perbraukite ekrano krašte"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Jei norite, kad būtų rodoma sistemos juosta, perbraukite iš ekrano krašto"</string> </resources> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 85ab3fd..b2bee8c 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Izv. sav. ar Wi-Fi"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Notiek GPS meklēšana..."</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS iestatītā atrašanās vieta"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Aktīvi atrašanās vietu pieprasījumi"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Notīrīt visus paziņojumus"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informācija par lietotni"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ekrāns tiks pagriezts automātiski."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMĀTISKI"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Šeit tiek rādīti paziņojumi"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Piekļūstiet tiem jebkurā laikā, velkot uz leju.\nVēlreiz velciet, lai tiktu parādītas sistēmas vadīklas."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Velciet no ekrāna malas, lai piekļūtu joslai."</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Velciet no ekrāna malas, lai piekļūtu sistēmas joslai."</string> </resources> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index b6b3577..9c76eae 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Pemberitahuan dipaparkan di sini"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Akses panel pada bila-bila masa dengan meleret ke bawah.\nLeret ke bawah sekali lagi untuk mendapatkan kawalan sistem."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Leret ke bahagian tepi skrin untuk menampakkan bar"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Leret dari tepi skrin untuk menampakkan bar sistem"</string> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 6f7d727..d850cf3 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi tilkoblet"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Søker etter GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Posisjon angitt av GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Aktive stedsforespørsler"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Fjern alle varslinger."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Info om app"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Skjermen roterer automatisk."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Varslene vises her"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Bruk dem når som helst ved å sveipe nedover.\nSveip nedover igjen for å gå til systemkontrollene."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Sveip på kanten av skjermen for å få frem feltet"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Sveip fra kanten på skjermen for å få frem systemfeltet"</string> </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 02a5b45..5b6ebab 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATISCH"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Meldingen worden hier weergegeven"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"U kunt de meldingen op elk gewenst moment openen door met uw vinger omlaag te vegen.\nVeeg nogmaals met uw vinger omlaag om de systeembesturingselementen weer te geven."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Veeg vanaf de rand om balk weer te geven"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Veeg vanaf de rand van het scherm om de systeembalk weer te geven"</string> </resources> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index d17c45e..6bff7af 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: połączono"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Wyszukiwanie sygnału GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Lokalizacja z GPSa"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Prośby o lokalizację są aktywne"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Usuń wszystkie powiadomienia."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"O aplikacji"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ekran zostanie obrócony automatycznie."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATYCZNA"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Tutaj pokazują się powiadomienia"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Możesz je otworzyć w dowolnej chwili, przesuwając w dół.\nPrzesuń jeszcze raz w dół, by otworzyć ustawienia systemowe."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Przesuń palcem od krawędzi ekranu, by odkryć pasek"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Przesuń palcem od krawędzi ekranu, by odkryć pasek systemu"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index b520319..6aa94e0 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ligado"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"A procurar GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Localização definida por GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Pedidos de localização ativos"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Limpar todas as notificações."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informações da aplicação"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"O ecrã será rodado automaticamente."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMÁTICO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"As notificações são apresentadas aqui"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Pode aceder em qualquer altura, deslizando rapidamente para baixo com o dedo.\nDeslize novamente para baixo para aceder aos controlos do sistema."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Deslize da extremidade do ecrã para revelar a barra"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Deslize da extremidade do ecrã para revelar a barra do sistema"</string> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 2a2f336..aa972fb 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Buscando GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Local definido por GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Solicitações de localização ativas"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Limpar todas as notificações."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informações do aplicativo"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"A tela girará automaticamente."</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"As notificações aparecem aqui"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Acesse a qualquer momento deslizando para baixo.\nDeslize para baixo novamente para acessar os controles do sistema."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Deslize a borda da tela para ver a barra"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Deslize a partir da borda da tela ver a barra do sistema"</string> </resources> diff --git a/packages/SystemUI/res/values-rm/strings.xml b/packages/SystemUI/res/values-rm/strings.xml index 22a857e..05b7453 100644 --- a/packages/SystemUI/res/values-rm/strings.xml +++ b/packages/SystemUI/res/values-rm/strings.xml @@ -372,8 +372,4 @@ <skip /> <!-- no translation found for status_bar_help_text (7874607155052076323) --> <skip /> - <!-- no translation found for hiding_navigation_confirmation_message (3227814171674734332) --> - <skip /> - <!-- no translation found for hiding_navigation_confirmation_message_long (7854368870786524950) --> - <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 375d12c..9c5ad00 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectat"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Se caută GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Locaţie setată prin GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Solicitări locație active"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Ștergeţi toate notificările."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informaţii despre aplicaţie"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ecranul se va roti în mod automat."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMAT"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Notificările se afişează aici"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Accesaţi-le oricând glisând în jos.\nGlisaţi în jos din nou pentru comenzile sistemului."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Glisați dinspre marginea ecranului pentru a afișa bara"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Glisați dinspre marginea ecranului pentru a afișa bara de sistem"</string> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 15adf90..3020624 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi подключено"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Поиск GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Координаты по GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Есть активные запросы на определение местоположения"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Удалить все уведомления"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"О приложении"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Экран будет поворачиваться автоматически."</string> @@ -206,6 +205,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"АВТОНАСТРОЙКА"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Это панель уведомлений"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Ее можно открыть, пролистнув экран вниз.\nЧтобы открыть настройки, проведите пальцем вниз ещё раз."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Чтобы открыть панель, проведите пальцем от края к центру экрана"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Чтобы открыть панель навигации, проведите пальцем от края к центру экрана"</string> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index bb70f36..59af9ab 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: pripojené"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Vyhľadávanie satelitov GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Poloha nastavená pomocou GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Žiadosti o polohu sú aktívne"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Vymazať všetky upozornenia."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informácie o aplikácii"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Obrazovka sa automaticky otočí."</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATICKY"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Tu sa zobrazujú upozornenia"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Môžete ich kedykoľvek zobraziť tak, že posuniete prstom nadol.\nAk posuniete prstom nadol ešte raz, zobrazia sa ovládacie prvky systému."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Panel zobrazíte posunutím cez okraj obrazovky"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Systémový panel zobrazíte posunutím cez okraj obrazovky"</string> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 46a5826..338ff44 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi povezan"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Iskanje GPS-a"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Lokacija nastavljena z GPS-om"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Aktivne zahteve za lokacijo"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Izbriši vsa obvestila."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Podatki o aplikaciji"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Zaslon se bo samodejno zasukal."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"SAMODEJNO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Obvestila so prikazana tukaj"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Do njih lahko kadar koli dostopate tako, da povlečete navzdol.\nZa prikaz sistemskih kontrolnikov znova povlecite navzdol."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Vrstico prikažete tako, da povlečete z roba zaslona"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Sistemsko vrstico prikažete tako, da povlečete z roba zaslona"</string> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 163bc06..b501d69 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"АУТОМАТСКА"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Обавештења се појављују овде"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Приступите им у било ком тренутку листањем надоле.\nПоново листајте надоле да би се приказале системске контроле."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Превуците по ивици екрана да би се приказала трака"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Превуците од ивице екрана да би се приказала системска трака"</string> </resources> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index c5335f4..560a00e 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi-ansluten"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Sökning efter GPS pågår"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Platsen har identifierats av GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Det finns aktiva platsbegäranden"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Ta bort alla meddelanden."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Info om appen"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Skärmen roteras automatiskt."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Meddelanden visas här"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Du kommer åt dem när som helst genom att dra nedåt.\nDra nedåt igen om du vill visa systemkontroller."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Dra från kanten av skärmen om du vill visa fältet"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Dra från kanten av skärmen om du vill visa systemfältet"</string> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index a0fb0a8..e3338de 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -199,6 +199,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"KIOTOMATIKI"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Arifa zitaonekana hapa"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Zifikie wakati wowote kwa kutelezesha chini.\nTelezesha chini tena kupata vidhibiti vya mfumo."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Telezesha kidole kutoka ukingo wa skrini ili kuonyesha upau"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Telezesha kidole kutoka ukingo wa skrini ili kuonyesha upau wa mfumo"</string> </resources> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 2552e5c..3127eb3 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"อัตโนมัติ"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"การแจ้งเตือนจะแสดงขึ้นที่นี่"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"เข้าถึงได้ทุกเมื่อด้วยการกวาดนิ้วลง\nกวาดนิ้วลงอีกครั้งสำหรับการควบคุมระบบ"</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"กวาดขอบของหน้าจอเพื่อแสดงแถบ"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"กวาดจากขอบของหน้าจอเพื่อแสดงแถบระบบ"</string> </resources> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index dfc6c7d..9d4d9de 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Dito lumalabas ang mga notification"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"I-access ang mga ito anumang oras sa pamamagitan ng pag-swipe pababa.\nMuling mag-swipe pababa para sa mga kontrol ng system."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Mag-swipe sa gilid ng screen upang ipakita ang bar"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Mag-swipe mula sa gilid ng screen upang ipakita ang system bar"</string> </resources> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index f7b34de..b885344 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Kablosuz bağlandı"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS aranıyor"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Konum GPS ile belirlendi"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Konum bilgisi istekleri etkin"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Tüm bildirimleri temizle"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Uygulama bilgileri"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ekran otomatik olarak dönecektir."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"OTOMATİK"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Bildirimler burada görünür"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Aşağıya hızlıca kaydırarak bunlara istediğiniz zaman erişebilirsiniz.\nSistem denetimleri için tekrar hızlıca aşağı kaydırın."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Çubuğu görüntülemek için ekranın kenarından hızlıca kaydırın"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Sistem çubuğunu görüntülemek için ekranın kenarından hızlıca kaydırın"</string> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 23e276b..1f3c131 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi під’єднано"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Виконується пошук GPS-сигналу"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Місцезнаходження встановлено за допомогою GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Запити про місцезнаходження активні"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Очистити всі сповіщення."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Інформація про програму"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Екран обертатиметься автоматично."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"АВТО"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Сповіщення з’являються тут"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Отримуйте до них доступ будь-коли, провівши пальцем униз.\nЗнову проведіть униз, щоб відкрити елементи керування системи."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Гортайте від краю екрана, щоб з’явилась панель"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Проведіть пальцем від краю екрана, щоб з’явилась навігаційна панель"</string> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 31fc2c8..01ec999 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -164,8 +164,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Đã kết nối Wi-Fi"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"Đang tìm kiếm GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"Vị trí đặt bởi GPS"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"Yêu cầu về thông tin vị trí hiện hoạt"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"Xóa tất cả thông báo."</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Thông tin về ứng dụng"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Màn hình sẽ xoay tự động."</string> @@ -202,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"TỰ ĐỘNG"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Thông báo xuất hiện tại đây"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Truy cập vào chúng bất kỳ lúc nào bằng cách vuốt xuống.\nVuốt lại xuống để hiển thị các điều khiển hệ thống."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Vuốt cạnh màn hình để hiển thị thanh"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Vuốt từ cạnh màn hình để hiển thị thanh hệ thống"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index b8b56c9..867cb17 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -166,8 +166,7 @@ <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"WLAN 已连接"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"正在搜索 GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"已通过 GPS 确定位置"</string> - <!-- no translation found for accessibility_location_active (2427290146138169014) --> - <skip /> + <string name="accessibility_location_active" msgid="2427290146138169014">"应用发出了有效位置信息请求"</string> <string name="accessibility_clear_all" msgid="5235938559247164925">"清除所有通知。"</string> <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"应用信息"</string> <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"屏幕会自动旋转。"</string> @@ -204,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"自动"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"通知会显示在这里"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"向下滑动可随时查看通知。\n再次向下滑动可使用系统控制功能。"</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"从屏幕边缘向里滑可显示系统栏"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"从屏幕边缘向里滑动即可显示系统栏"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index a0bb92a..1d5b2ac 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -203,6 +203,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"自動"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"系統會在這裡顯示通知"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"向下滑動即可隨時存取通知。\n再次向下滑動即可使用系統控制項。"</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"從螢幕邊緣向內滑動即可顯示導覽列"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"從螢幕邊緣向內滑動即可顯示導覽列"</string> </resources> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 53e7db0..662d3cb 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -201,6 +201,4 @@ <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"OKUZENZAKALELAYO"</string> <string name="status_bar_help_title" msgid="1199237744086469217">"Izaziso zivela lapha"</string> <string name="status_bar_help_text" msgid="7874607155052076323">"Kufinyelele noma kunini ngokuswayiphela phansi.\nSwayiphela phansi futhi ngezilawuli zesistimu."</string> - <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Swayipha kunqenqema lwesikrini ukuze uveze ibha"</string> - <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Swayipha kusuka kunqenqema ukuze uveze ibha yesistimu"</string> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 04de60f..5718db2 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -20,7 +20,7 @@ <drawable name="notification_number_text_color">#ffffffff</drawable> <drawable name="ticker_background_color">#ff1d1d1d</drawable> <drawable name="status_bar_background">#ff000000</drawable> - <color name="status_bar_background_transient">#55000000</color> + <color name="status_bar_background_semi_transparent">#55000000</color> <color name="status_bar_background_transparent">#00000000</color> <color name="navigation_bar_background_transparent_start">#7f000000</color> <color name="navigation_bar_background_transparent_end">#00000000</color> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index f17b143..ff84243 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -135,9 +135,9 @@ public class SignalClusterView public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // Standard group layout onPopulateAccessibilityEvent() implementations // ignore content description, so populate manually - if (mWifiVisible && mWifiGroup.getContentDescription() != null) + if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null) event.getText().add(mWifiGroup.getContentDescription()); - if (mMobileVisible && mMobileGroup.getContentDescription() != null) + if (mMobileVisible && mMobileGroup != null && mMobileGroup.getContentDescription() != null) event.getText().add(mMobileGroup.getContentDescription()); return super.dispatchPopulateAccessibilityEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java index 74b14b0..e40c4e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java @@ -16,34 +16,47 @@ package com.android.systemui.statusbar.phone; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.util.Log; import android.view.View; import com.android.systemui.R; public class BarTransitions { + private static final boolean DEBUG = false; - public static final int MODE_NORMAL = 0; - public static final int MODE_TRANSIENT = 1; + public static final int MODE_OPAQUE = 0; + public static final int MODE_SEMI_TRANSPARENT = 1; public static final int MODE_TRANSPARENT = 2; + private final String mTag; private final View mTarget; - private final Drawable mOpaque; - private final Drawable mTransient; + private final int mOpaque; + private final int mSemiTransparent; - private Drawable mTransparent; + protected Drawable mTransparent; private int mMode; - public BarTransitions(Context context, View target, Drawable transparent) { + private final AnimatorUpdateListener mBackgroundColorListener = new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + mTarget.setBackgroundColor((Integer) animator.getAnimatedValue()); + } + }; + + public BarTransitions(Context context, View target) { + mTag = "BarTransitions." + target.getClass().getSimpleName(); mTarget = target; final Resources res = context.getResources(); - mOpaque = new ColorDrawable(res.getColor(R.drawable.status_bar_background)); - mTransient = new ColorDrawable(res.getColor(R.color.status_bar_background_transient)); - mTransparent = transparent; + mOpaque = res.getColor(R.drawable.status_bar_background); + mSemiTransparent = res.getColor(R.color.status_bar_background_semi_transparent); } public void setTransparent(Drawable transparent) { @@ -54,11 +67,42 @@ public class BarTransitions { } public void transitionTo(int mode) { + transitionTo(mode, false); + } + + public void transitionTo(int mode, boolean animate) { + if (mMode == mode) return; + int oldMode = mMode; mMode = mode; if (!ActivityManager.isHighEndGfx()) return; - Drawable background = mode == MODE_TRANSIENT ? mTransient - : mode == MODE_TRANSPARENT ? mTransparent - : mOpaque; - mTarget.setBackground(background); + if (DEBUG) Log.d(mTag, modeToString(oldMode) + " -> " + modeToString(mode)); + onTransition(oldMode, mMode, animate); + } + + protected void onTransition(int oldMode, int newMode, boolean animate) { + if (animate && oldMode == MODE_SEMI_TRANSPARENT && newMode == MODE_OPAQUE) { + startColorAnimation(mSemiTransparent, mOpaque); + } else if (animate && oldMode == MODE_OPAQUE && newMode == MODE_SEMI_TRANSPARENT) { + startColorAnimation(mOpaque, mSemiTransparent); + } else if (newMode == MODE_OPAQUE || newMode == MODE_SEMI_TRANSPARENT) { + mTarget.setBackgroundColor(newMode == MODE_OPAQUE ? mOpaque : mSemiTransparent); + } else { + mTarget.setBackground(newMode == MODE_TRANSPARENT? mTransparent + : newMode == MODE_SEMI_TRANSPARENT ? new ColorDrawable(mSemiTransparent) + : new ColorDrawable(mOpaque)); + } + } + + private void startColorAnimation(int from, int to) { + ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), from, to); + anim.addUpdateListener(mBackgroundColorListener); + anim.start(); + } + + public static String modeToString(int mode) { + if (mode == MODE_OPAQUE) return "MODE_OPAQUE"; + if (mode == MODE_SEMI_TRANSPARENT) return "MODE_SEMI_TRANSPARENT"; + if (mode == MODE_TRANSPARENT) return "MODE_TRANSPARENT"; + throw new IllegalArgumentException("Unknown mode " + mode); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 131713f..62f8596 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -47,6 +47,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DelegateViewHelper; import com.android.systemui.statusbar.policy.DeadZone; +import com.android.systemui.statusbar.policy.KeyButtonView; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -81,9 +82,7 @@ public class NavigationBarView extends LinearLayout { private DelegateViewHelper mDelegateHelper; private DeadZone mDeadZone; - private final BarTransitions mBarTransitions; - private final Drawable mTransparent; - private final Drawable mTransparentVertical; + private final NavigationBarTransitions mBarTransitions; // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) final static boolean WORKAROUND_INVALID_LAYOUT = true; @@ -112,6 +111,43 @@ public class NavigationBarView extends LinearLayout { } } + private final class NavigationBarTransitions extends BarTransitions { + + private final Drawable mTransparentBottom; + private final Drawable mTransparentRight; + + public NavigationBarTransitions(Context context) { + super(context, NavigationBarView.this); + final Resources res = mContext.getResources(); + final int[] gradientColors = new int[] { + res.getColor(R.color.navigation_bar_background_transparent_start), + res.getColor(R.color.navigation_bar_background_transparent_end) + }; + mTransparentBottom = new GradientDrawable(Orientation.BOTTOM_TOP, gradientColors); + mTransparentRight = new GradientDrawable(Orientation.RIGHT_LEFT, gradientColors); + } + + public void setVertical(boolean isVertical) { + mTransparent = isVertical ? mTransparentRight : mTransparentBottom; + } + + @Override + protected void onTransition(int oldMode, int newMode, boolean animate) { + super.onTransition(oldMode, newMode, animate); + final float alpha = newMode == MODE_OPAQUE ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; + setKeyButtonViewQuiescentAlpha(getBackButton(), alpha); + setKeyButtonViewQuiescentAlpha(getHomeButton(), alpha); + setKeyButtonViewQuiescentAlpha(getRecentsButton(), alpha); + setKeyButtonViewQuiescentAlpha(getMenuButton(), alpha); + } + + private void setKeyButtonViewQuiescentAlpha(View button, float alpha) { + if (button instanceof KeyButtonView) { + ((KeyButtonView) button).setQuiescentAlpha(alpha); + } + } + } + public NavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -130,13 +166,7 @@ public class NavigationBarView extends LinearLayout { getIcons(res); - final int[] gradientColors = new int[] { - res.getColor(R.color.navigation_bar_background_transparent_start), - res.getColor(R.color.navigation_bar_background_transparent_end) - }; - mTransparent = new GradientDrawable(Orientation.BOTTOM_TOP, gradientColors); - mTransparentVertical = new GradientDrawable(Orientation.RIGHT_LEFT, gradientColors); - mBarTransitions = new BarTransitions(context, this, mTransparent); + mBarTransitions = new NavigationBarTransitions(context); } public BarTransitions getBarTransitions() { @@ -423,7 +453,7 @@ public class NavigationBarView extends LinearLayout { } setNavigationIconHints(mNavigationIconHints, true); - mBarTransitions.setTransparent(mVertical ? mTransparentVertical : mTransparent); + mBarTransitions.setVertical(mVertical); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index b78239b..4b2c3e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -76,6 +76,7 @@ public class PanelView extends FrameLayout { private boolean mClosing; private boolean mRubberbanding; private boolean mTracking; + private int mTrackingPointer; private TimeAnimator mTimeAnimator; private ObjectAnimator mPeekAnimator; @@ -380,14 +381,21 @@ public class PanelView extends FrameLayout { mHandleView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - final float y = event.getY(); - final float rawY = event.getRawY(); - if (DEBUG) logf("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f", + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float y = event.getY(pointerIndex); + final float rawDelta = event.getRawY() - event.getY(); + final float rawY = y + rawDelta; + if (DEBUG) logf("handle.onTouch: a=%s p=[%d,%d] y=%.1f rawY=%.1f off=%.1f", MotionEvent.actionToString(event.getAction()), + mTrackingPointer, pointerIndex, y, rawY, mTouchOffset); PanelView.this.getLocationOnScreen(mAbsPos); - switch (event.getAction()) { + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mTracking = true; mHandleView.setPressed(true); @@ -397,13 +405,26 @@ public class PanelView extends FrameLayout { trackMovement(event); mTimeAnimator.cancel(); // end any outstanding animations mBar.onTrackingStarted(PanelView.this); - mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight(); + mTouchOffset = (rawY - mAbsPos[1]) - mExpandedHeight; if (mExpandedHeight == 0) { mJustPeeked = true; runPeekAnimation(); } break; + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newRawY = newY + rawDelta; + mTrackingPointer = event.getPointerId(newIndex); + mTouchOffset = (newRawY - mAbsPos[1]) - mExpandedHeight; + mInitialTouchY = newY; + } + break; + case MotionEvent.ACTION_MOVE: final float h = rawY - mAbsPos[1] - mTouchOffset; if (h > mPeekHeight) { @@ -424,6 +445,7 @@ public class PanelView extends FrameLayout { case MotionEvent.ACTION_CANCEL: mFinalTouchY = y; mTracking = false; + mTrackingPointer = -1; mHandleView.setPressed(false); postInvalidate(); // catch the press state change mBar.onTrackingStopped(PanelView.this); @@ -622,6 +644,10 @@ public class PanelView extends FrameLayout { return mClosing; } + public boolean isTracking() { + return mTracking; + } + public void setBar(PanelBar panelBar) { mBar = panelBar; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 62be5d6..ad53fea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_NORMAL; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSIENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; import android.animation.Animator; @@ -935,7 +935,7 @@ public class PhoneStatusBar extends BaseStatusBar { } if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 - && !mStatusBarWindow.isPointerDown()) { + && !mNotificationPanel.isTracking()) { animateCollapsePanels(); } } @@ -1912,7 +1912,7 @@ public class PhoneStatusBar extends BaseStatusBar { if (sbMode != -1 || nbMode != -1) { // update transient bar autohide - if (sbMode == MODE_TRANSIENT || nbMode == MODE_TRANSIENT) { + if (sbMode == MODE_SEMI_TRANSPARENT || nbMode == MODE_SEMI_TRANSPARENT) { scheduleAutohide(); } else { cancelAutohide(); @@ -1936,15 +1936,23 @@ public class PhoneStatusBar extends BaseStatusBar { } private int barMode(int vis, int transientFlag, int transparentFlag) { - return (vis & transientFlag) != 0 ? MODE_TRANSIENT + return (vis & transientFlag) != 0 ? MODE_SEMI_TRANSPARENT : (vis & transparentFlag) != 0 ? MODE_TRANSPARENT - : MODE_NORMAL; + : MODE_OPAQUE; } @Override public void resumeAutohide() { if (mAutohideSuspended) { scheduleAutohide(); + animateTransitionTo(BarTransitions.MODE_SEMI_TRANSPARENT); + } + } + + private void animateTransitionTo(int newMode) { + mStatusBarView.getBarTransitions().transitionTo(newMode, true /*animate*/); + if (mNavigationBarView != null) { + mNavigationBarView.getBarTransitions().transitionTo(newMode, true /*animate*/); } } @@ -1952,6 +1960,7 @@ public class PhoneStatusBar extends BaseStatusBar { public void suspendAutohide() { mHandler.removeCallbacks(mAutohide); mAutohideSuspended = 0 != (mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT); + animateTransitionTo(BarTransitions.MODE_OPAQUE); } private void cancelAutohide() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index ea84c5c..910d4c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -46,7 +46,15 @@ public class PhoneStatusBarView extends PanelBar { PanelView mLastFullyOpenedPanel = null; PanelView mNotificationPanel, mSettingsPanel; private boolean mShouldFade; - private final BarTransitions mBarTransitions; + private final StatusBarTransitions mBarTransitions; + + private final class StatusBarTransitions extends BarTransitions { + public StatusBarTransitions(Context context) { + super(context, PhoneStatusBarView.this); + final Resources res = context.getResources(); + mTransparent = res.getDrawable(R.color.status_bar_background_transparent); + } + } public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -60,8 +68,7 @@ public class PhoneStatusBarView extends PanelBar { mSettingsPanelDragzoneFrac = 0f; } mFullWidthNotifications = mSettingsPanelDragzoneFrac <= 0f; - final Drawable transparent = res.getDrawable(R.color.status_bar_background_transparent); - mBarTransitions = new BarTransitions(context, this, transparent); + mBarTransitions = new StatusBarTransitions(context); } public BarTransitions getBarTransitions() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 800bc02..a600aae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -42,7 +42,6 @@ public class StatusBarWindowView extends FrameLayout private NotificationRowLayout latestItems; private NotificationPanelView mNotificationPanel; private ScrollView mScrollView; - private boolean mPointerDown; PhoneStatusBar mService; @@ -87,7 +86,6 @@ public class StatusBarWindowView extends FrameLayout @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - registerPointer(ev); boolean intercept = false; if (mNotificationPanel.isFullyExpanded() && mScrollView.getVisibility() == View.VISIBLE) { intercept = mExpandHelper.onInterceptTouchEvent(ev); @@ -133,21 +131,5 @@ public class StatusBarWindowView extends FrameLayout mExpandHelper.cancel(); } } - - private void registerPointer(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mPointerDown = true; - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mPointerDown = false; - break; - } - } - - public boolean isPointerDown() { - return mPointerDown; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index f325957..924478c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.policy; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; @@ -26,6 +29,7 @@ import android.graphics.drawable.Drawable; import android.hardware.input.InputManager; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -41,9 +45,10 @@ import com.android.systemui.R; public class KeyButtonView extends ImageView { private static final String TAG = "StatusBar.KeyButtonView"; + private static final boolean DEBUG = false; final float GLOW_MAX_SCALE_FACTOR = 1.8f; - final float BUTTON_QUIESCENT_ALPHA = 0.70f; + public static final float DEFAULT_QUIESCENT_ALPHA = 0.70f; long mDownTime; int mCode; @@ -51,6 +56,7 @@ public class KeyButtonView extends ImageView { Drawable mGlowBG; int mGlowWidth, mGlowHeight; float mGlowAlpha = 0f, mGlowScale = 1f, mDrawingAlpha = 1f; + float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; boolean mSupportsLongpress = true; RectF mRect = new RectF(0f,0f,0f,0f); AnimatorSet mPressedAnim; @@ -70,6 +76,15 @@ public class KeyButtonView extends ImageView { } }; + private final AnimatorListener mRecoverToQuiescentListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mQuiescentAlpha != mDrawingAlpha) { + animateToQuiescent().setDuration(200).start(); + } + } + }; + public KeyButtonView(Context context, AttributeSet attrs) { this(context, attrs, 0); } @@ -86,7 +101,7 @@ public class KeyButtonView extends ImageView { mGlowBG = a.getDrawable(R.styleable.KeyButtonView_glowBackground); if (mGlowBG != null) { - setDrawingAlpha(BUTTON_QUIESCENT_ALPHA); + setDrawingAlpha(mQuiescentAlpha); mGlowWidth = mGlowBG.getIntrinsicWidth(); mGlowHeight = mGlowBG.getIntrinsicHeight(); } @@ -118,6 +133,16 @@ public class KeyButtonView extends ImageView { super.onDraw(canvas); } + public void setQuiescentAlpha(float alpha) { + alpha = Math.min(Math.max(alpha, 0), 1); + if (alpha == mQuiescentAlpha) return; + mQuiescentAlpha = alpha; + if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); + if (mGlowBG != null) { + setDrawingAlpha(mQuiescentAlpha); + } + } + public float getDrawingAlpha() { if (mGlowBG == null) return 0; return mDrawingAlpha; @@ -172,6 +197,12 @@ public class KeyButtonView extends ImageView { } } + private ObjectAnimator animateToQuiescent() { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); + anim.addListener(mRecoverToQuiescentListener); // mQuiescentAlpha may change mid-animation + return anim; + } + public void setPressed(boolean pressed) { if (mGlowBG != null) { if (pressed != isPressed()) { @@ -182,8 +213,8 @@ public class KeyButtonView extends ImageView { if (pressed) { if (mGlowScale < GLOW_MAX_SCALE_FACTOR) mGlowScale = GLOW_MAX_SCALE_FACTOR; - if (mGlowAlpha < BUTTON_QUIESCENT_ALPHA) - mGlowAlpha = BUTTON_QUIESCENT_ALPHA; + if (mGlowAlpha < mQuiescentAlpha) + mGlowAlpha = mQuiescentAlpha; setDrawingAlpha(1f); as.playTogether( ObjectAnimator.ofFloat(this, "glowAlpha", 1f), @@ -194,7 +225,7 @@ public class KeyButtonView extends ImageView { as.playTogether( ObjectAnimator.ofFloat(this, "glowAlpha", 0f), ObjectAnimator.ofFloat(this, "glowScale", 1f), - ObjectAnimator.ofFloat(this, "drawingAlpha", BUTTON_QUIESCENT_ALPHA) + animateToQuiescent() ); as.setDuration(500); } diff --git a/policy/src/com/android/internal/policy/impl/BarController.java b/policy/src/com/android/internal/policy/impl/BarController.java new file mode 100644 index 0000000..fb76e20 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/BarController.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.app.StatusBarManager; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; +import android.view.View; +import android.view.WindowManagerPolicy.WindowState; + +import com.android.internal.statusbar.IStatusBarService; + +import java.io.PrintWriter; + +/** + * Controls state/behavior specific to a system bar window. + */ +public class BarController { + private static final boolean DEBUG = false; + + private static final int TRANSIENT_BAR_NONE = 0; + private static final int TRANSIENT_BAR_SHOWING = 1; + private static final int TRANSIENT_BAR_HIDING = 2; + + private final String mTag; + private final int mTransientFlag; + private final int mStatusBarManagerId; + private final Handler mHandler; + private final Object mServiceAquireLock = new Object(); + private IStatusBarService mStatusBarService; + + private WindowState mWin; + private int mTransientBarState; + private boolean mPendingShow; + + public BarController(String tag, int transientFlag, int statusBarManagerId) { + mTag = "BarController." + tag; + mTransientFlag = transientFlag; + mStatusBarManagerId = statusBarManagerId; + mHandler = new Handler(); + } + + public void setWindow(WindowState win) { + mWin = win; + } + + public void showTransient() { + if (mWin != null) { + setTransientBarState(TRANSIENT_BAR_SHOWING); + } + } + + public boolean isTransientShowing() { + return mTransientBarState == TRANSIENT_BAR_SHOWING; + } + + public void adjustSystemUiVisibilityLw(int visibility) { + if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING && + (visibility & mTransientFlag) == 0) { + setTransientBarState(TRANSIENT_BAR_HIDING); + setBarShowingLw(false); + } + } + + public boolean setBarShowingLw(final boolean show) { + if (mWin == null) return false; + + mHandler.post(new Runnable() { + @Override + public void run() { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.setWindowState(mStatusBarManagerId, show + ? StatusBarManager.WINDOW_STATE_SHOWING + : StatusBarManager.WINDOW_STATE_HIDING); + } + } catch (RemoteException e) { + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + }); + if (show && mTransientBarState == TRANSIENT_BAR_HIDING) { + mPendingShow = true; + return false; + } + return show ? mWin.showLw(true) : mWin.hideLw(true); + } + + public boolean checkHiddenLw() { + if (mWin != null && mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) { + // Finished animating out, clean up and reset style + setTransientBarState(TRANSIENT_BAR_NONE); + if (mPendingShow) { + setBarShowingLw(true); + mPendingShow = false; + } + return true; + } + return false; + } + + public boolean checkShowTransientBarLw() { + if (mTransientBarState == TRANSIENT_BAR_SHOWING) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown"); + return false; + } else if (mWin == null) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist"); + return false; + } else if (mWin.isDisplayedLw()) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible"); + return false; + } else { + return true; + } + } + + public int updateVisibilityLw(boolean allowed, int oldVis, int vis) { + if (mWin == null) return vis; + + if (mTransientBarState == TRANSIENT_BAR_SHOWING) { // transient bar requested + if (allowed) { + vis |= mTransientFlag; + if ((oldVis & mTransientFlag) == 0) { + setBarShowingLw(true); + } + } else { + setTransientBarState(TRANSIENT_BAR_NONE); // request denied + } + } + if (mTransientBarState != TRANSIENT_BAR_NONE) { + vis |= mTransientFlag; // ignore clear requests until transition completes + vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile + } + return vis; + } + + private void setTransientBarState(int state) { + if (mWin != null && state != mTransientBarState) { + mTransientBarState = state; + if (DEBUG) Slog.d(mTag, "New state: " + transientBarStateToString(state)); + } + } + + private IStatusBarService getStatusBarService() { + synchronized (mServiceAquireLock) { + if (mStatusBarService == null) { + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService("statusbar")); + } + return mStatusBarService; + } + } + + private static String transientBarStateToString(int state) { + if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING"; + if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING"; + if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE"; + throw new IllegalArgumentException("Unknown state " + state); + } + + public void dump(PrintWriter pw, String prefix) { + if (mWin != null) { + pw.print(prefix); pw.print(mTag); pw.print(' '); + pw.print("mTransientBar"); pw.print('='); + pw.println(transientBarStateToString(mTransientBarState)); + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 9da4357..11e33dc 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -553,11 +553,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } MyOrientationListener mOrientationListener; - private static final int TRANSIENT_BAR_NONE = 0; - private static final int TRANSIENT_BAR_SHOWING = 1; - private static final int TRANSIENT_BAR_HIDING = 2; - private int mStatusTransientBar; - private int mNavigationTransientBar; + private final BarController mStatusBarController = new BarController("StatusBar", + View.STATUS_BAR_TRANSIENT, StatusBarManager.WINDOW_STATUS_BAR); + private final BarController mNavigationBarController = new BarController("NavigationBar", + View.NAVIGATION_BAR_TRANSIENT, StatusBarManager.WINDOW_NAVIGATION_BAR); private TransientNavigationConfirmation mTransientNavigationConfirmation; private SystemGesturesPointerEventListener mSystemGestures; @@ -1716,6 +1715,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } mStatusBar = win; + mStatusBarController.setWindow(win); break; case TYPE_NAVIGATION_BAR: mContext.enforceCallingOrSelfPermission( @@ -1727,6 +1727,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } mNavigationBar = win; + mNavigationBarController.setWindow(win); if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); break; case TYPE_NAVIGATION_BAR_PANEL: @@ -1765,6 +1766,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void removeWindowLw(WindowState win) { if (mStatusBar == win) { mStatusBar = null; + mStatusBarController.setWindow(null); } else if (mKeyguard == win) { Log.v(TAG, "Removing keyguard window (Did it crash?)"); mKeyguard = null; @@ -1774,6 +1776,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardScrim = null; } if (mNavigationBar == win) { mNavigationBar = null; + mNavigationBarController.setWindow(null); } } @@ -2548,16 +2551,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public int adjustSystemUiVisibilityLw(int visibility) { - if (mStatusBar != null && mStatusTransientBar == TRANSIENT_BAR_SHOWING && - 0 == (visibility & View.STATUS_BAR_TRANSIENT)) { - mStatusTransientBar = TRANSIENT_BAR_HIDING; - setBarShowingLw(mStatusBar, false); - } - if (mNavigationBar != null && mNavigationTransientBar == TRANSIENT_BAR_SHOWING && - 0 == (visibility & View.NAVIGATION_BAR_TRANSIENT)) { - mNavigationTransientBar = TRANSIENT_BAR_HIDING; - setBarShowingLw(mNavigationBar, false); - } + mStatusBarController.adjustSystemUiVisibilityLw(visibility); + mNavigationBarController.adjustSystemUiVisibilityLw(visibility); + // Reset any bits in mForceClearingStatusBarVisibility that // are now clear. mResettingSystemUiFlags &= visibility; @@ -2714,7 +2710,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean updateSysUiVisibility = false; if (mNavigationBar != null) { - boolean transientNavBarShowing = mNavigationTransientBar == TRANSIENT_BAR_SHOWING; + boolean transientNavBarShowing = mNavigationBarController.isTransientShowing(); // Force the navigation bar to its appropriate place and // size. We need to do this directly, instead of relying on // it to bubble up from the nav bar, because this needs to @@ -2727,15 +2723,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom); mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top; if (transientNavBarShowing || navTransparent) { - setBarShowingLw(mNavigationBar, true); + mNavigationBarController.setBarShowingLw(true); } else if (navVisible) { - setBarShowingLw(mNavigationBar, true); + mNavigationBarController.setBarShowingLw(true); mDockBottom = mTmpNavigationFrame.top; mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop; mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop; } else { // We currently want to hide the navigation UI. - setBarShowingLw(mNavigationBar, false); + mNavigationBarController.setBarShowingLw(false); } if (navVisible && !navTransparent && !mNavigationBar.isAnimatingLw()) { // If the opaque nav bar is currently requested to be visible, @@ -2750,15 +2746,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight); mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left; if (transientNavBarShowing || navTransparent) { - setBarShowingLw(mNavigationBar, true); + mNavigationBarController.setBarShowingLw(true); } else if (navVisible) { - setBarShowingLw(mNavigationBar, true); + mNavigationBarController.setBarShowingLw(true); mDockRight = mTmpNavigationFrame.left; mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft; mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft; } else { // We currently want to hide the navigation UI. - setBarShowingLw(mNavigationBar, false); + mNavigationBarController.setBarShowingLw(false); } if (navVisible && !navTransparent && !mNavigationBar.isAnimatingLw()) { // If the nav bar is currently requested to be visible, @@ -2778,9 +2774,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame); if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame); - if (mNavigationTransientBar == TRANSIENT_BAR_HIDING && !mNavigationBar.isVisibleLw()) { - // Finished animating out, clean up and reset alpha - mNavigationTransientBar = TRANSIENT_BAR_NONE; + if (mNavigationBarController.checkHiddenLw()) { updateSysUiVisibility = true; } } @@ -2838,10 +2832,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // we can tell the app that it is covered by it. mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight; } - - if (mStatusTransientBar == TRANSIENT_BAR_HIDING && !mStatusBar.isVisibleLw()) { - // Finished animating out, clean up and reset alpha - mStatusTransientBar = TRANSIENT_BAR_NONE; + if (mStatusBarController.checkHiddenLw()) { updateSysUiVisibility = true; } } @@ -3410,7 +3401,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { + " top=" + mTopFullscreenOpaqueWindowState); if (mForceStatusBar || mForceStatusBarFromKeyguard) { if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced"); - if (setBarShowingLw(mStatusBar, true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; + if (mStatusBarController.setBarShowingLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } } else if (mTopFullscreenOpaqueWindowState != null) { if (localLOGV) { Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() @@ -3424,20 +3417,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { // and mTopIsFullscreen is that that mTopIsFullscreen is set only if the window // has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the // case though. - if (mStatusTransientBar == TRANSIENT_BAR_SHOWING) { - if (setBarShowingLw(mStatusBar, true)) { + if (mStatusBarController.isTransientShowing()) { + if (mStatusBarController.setBarShowingLw(true)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; } } else if (topIsFullscreen) { if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar"); - if (setBarShowingLw(mStatusBar, false)) { + if (mStatusBarController.setBarShowingLw(false)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; } else { if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar already hiding"); } } else { if (DEBUG_LAYOUT) Slog.v(TAG, "** SHOWING status bar: top is not fullscreen"); - if (setBarShowingLw(mStatusBar, true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; + if (mStatusBarController.setBarShowingLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } } } } @@ -3882,7 +3877,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_POWER: { result &= ~ACTION_PASS_TO_USER; if (down) { - if (isScreenOn && isNavigationBarTransient(mLastSystemUiFlags)) { + if (isScreenOn && isTransientNavigationAllowed(mLastSystemUiFlags)) { mTransientNavigationConfirmation.unconfirmLastPackage(); } if (isScreenOn && !mPowerKeyTriggered @@ -4153,36 +4148,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void requestTransientBars(WindowState swipeTarget) { synchronized (mWindowManagerFuncs.getWindowManagerLock()) { - boolean sb = checkShowTransientBar("status", mStatusTransientBar, mStatusBar); - boolean nb = checkShowTransientBar("nav", mNavigationTransientBar, mNavigationBar); + boolean sb = mStatusBarController.checkShowTransientBarLw(); + boolean nb = mNavigationBarController.checkShowTransientBarLw(); if (sb || nb) { WindowState barTarget = sb ? mStatusBar : mNavigationBar; if (sb ^ nb && barTarget != swipeTarget) { if (DEBUG) Slog.d(TAG, "Not showing transient bar, wrong swipe target"); return; } - mStatusTransientBar = sb ? TRANSIENT_BAR_SHOWING : mStatusTransientBar; - mNavigationTransientBar = nb ? TRANSIENT_BAR_SHOWING : mNavigationTransientBar; + if (sb) mStatusBarController.showTransient(); + if (nb) mNavigationBarController.showTransient(); updateSystemUiVisibilityLw(); } } } - private boolean checkShowTransientBar(String tag, int transientBar, WindowState win) { - if (transientBar == TRANSIENT_BAR_SHOWING) { - if (DEBUG) Slog.d(TAG, "Not showing " + tag + " transient bar, already shown"); - return false; - } else if (win == null) { - if (DEBUG) Slog.d(TAG, "Not showing " + tag + " transient bar, bar doesn't exist"); - return false; - } else if (win.isDisplayedLw()) { - if (DEBUG) Slog.d(TAG, "Not showing " + tag + " transient bar, bar already visible"); - return false; - } else { - return true; - } - } - @Override public void screenTurnedOff(int why) { EventLog.writeEvent(70000, 0); @@ -5057,103 +5037,62 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (ImmersiveModeTesting.enabled) { vis = ImmersiveModeTesting.applyForced(mFocusedWindow, vis); } + + // prevent status bar interaction from clearing certain flags boolean statusBarHasFocus = mFocusedWindow.getAttrs().type == TYPE_STATUS_BAR; if (statusBarHasFocus) { - // prevent status bar interaction from clearing certain flags int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT; vis = (vis & ~flags) | (mLastSystemUiFlags & flags); } - if (mStatusTransientBar == TRANSIENT_BAR_SHOWING) { - // status transient bar requested - boolean transientAllowed = - (vis & View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT) != 0; - boolean hideStatusBarWM = - (mFocusedWindow.getAttrs().flags - & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; - boolean hideStatusBarSysui = - (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; - - boolean transientStatusBarAllowed = - hideStatusBarWM - || (hideStatusBarSysui && transientAllowed) - || statusBarHasFocus; - - if (mStatusBar == null || !transientStatusBarAllowed) { - mStatusTransientBar = TRANSIENT_BAR_NONE; - if (mStatusBar != null && hideStatusBarSysui) { - // clear the clearable flags instead - int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS; - if (newVal != mResettingSystemUiFlags) { - mResettingSystemUiFlags = newVal; - mWindowManagerFuncs.reevaluateStatusBarVisibility(); - } - } - } else { - // show status transient bar - vis |= View.STATUS_BAR_TRANSIENT; - if ((mLastSystemUiFlags & View.STATUS_BAR_TRANSIENT) == 0) { - vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; - setBarShowingLw(mStatusBar, true); - } + + // update status bar + boolean transientAllowed = + (vis & View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT) != 0; + boolean hideStatusBarWM = + (mFocusedWindow.getAttrs().flags + & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + boolean hideStatusBarSysui = + (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; + + boolean transientStatusBarAllowed = + mStatusBar != null && ( + hideStatusBarWM + || (hideStatusBarSysui && transientAllowed) + || statusBarHasFocus); + + if (mStatusBarController.isTransientShowing() + && !transientStatusBarAllowed && hideStatusBarSysui) { + // clear the clearable flags instead + int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS; + if (newVal != mResettingSystemUiFlags) { + mResettingSystemUiFlags = newVal; + mWindowManagerFuncs.reevaluateStatusBarVisibility(); } } - boolean oldTransientNav = isNavigationBarTransient(oldVis); - boolean isTransientNav = isNavigationBarTransient(vis); + + vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis); + + // update navigation bar + boolean oldTransientNav = isTransientNavigationAllowed(oldVis); + boolean isTransientNav = isTransientNavigationAllowed(vis); if (mFocusedWindow != null && oldTransientNav != isTransientNav) { final int uid = getCurrentUserId(); final String pkg = mFocusedWindow.getOwningPackage(); mTransientNavigationConfirmation.transientNavigationChanged(uid, pkg, isTransientNav); } - if (mNavigationTransientBar == TRANSIENT_BAR_SHOWING) { - // navigation transient bar requested - if (!isTransientNav) { - mNavigationTransientBar = TRANSIENT_BAR_NONE; - } else { - // show navigation transient bar - vis |= View.NAVIGATION_BAR_TRANSIENT; - if ((mLastSystemUiFlags & View.NAVIGATION_BAR_TRANSIENT) == 0) { - vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; - setBarShowingLw(mNavigationBar, true); - } - } - } + vis = mNavigationBarController.updateVisibilityLw(isTransientNav, oldVis, vis); + return vis; } - private boolean isNavigationBarTransient(int vis) { + private boolean isTransientNavigationAllowed(int vis) { return mNavigationBar != null && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 && (vis & View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT) != 0; } - private boolean setBarShowingLw(WindowState win, final boolean show) { - final int window = - win == mStatusBar ? StatusBarManager.WINDOW_STATUS_BAR - : win == mNavigationBar ? StatusBarManager.WINDOW_NAVIGATION_BAR - : 0; - if (window != 0) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - IStatusBarService statusbar = getStatusBarService(); - if (statusbar != null) { - statusbar.setWindowState(window, show - ? StatusBarManager.WINDOW_STATE_SHOWING - : StatusBarManager.WINDOW_STATE_HIDING); - } - } catch (RemoteException e) { - // re-acquire status bar service next time it is needed. - mStatusBarService = null; - } - } - }); - } - return show ? win.showLw(true) : win.hideLw(true); - } - // Temporary helper that allows testing immersive mode on existing apps // TODO remove private static final class ImmersiveModeTesting { @@ -5414,5 +5353,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation); pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock); pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation); + mStatusBarController.dump(pw, prefix); + mNavigationBarController.dump(pw, prefix); } } diff --git a/services/input/InputReader.h b/services/input/InputReader.h index 98daaf5..a8bb636 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -1285,6 +1285,9 @@ protected: if (haveSizeBias) { *outSize += sizeBias; } + if (*outSize < 0) { + *outSize = 0; + } } } mCalibration; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index be6119d..3f58fa4 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -21,12 +21,12 @@ import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; - import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import android.app.AppOpsManager; import android.appwidget.AppWidgetManager; import android.util.ArrayMap; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; @@ -52,6 +52,7 @@ import com.android.server.pm.UserManagerService; import com.android.server.wm.AppTransition; import com.android.server.wm.StackBox; import com.android.server.wm.WindowManagerService; + import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -154,6 +155,7 @@ import android.os.UpdateLock; import android.os.UserHandle; import android.provider.Settings; import android.text.format.Time; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; @@ -536,7 +538,7 @@ public final class ActivityManagerService extends ActivityManagerNative * This is the process holding what we currently consider to be * the "home" activity. */ - ProcessRecord mHomeProcess; + ArraySet<ProcessRecord> mHomeProcess = new ArraySet<ProcessRecord>(); /** * This is the process holding the activity the user last visited that @@ -8951,11 +8953,11 @@ public final class ActivityManagerService extends ActivityManagerNative // replaced by a third-party app, clear the package preferred activities from packages // with a home activity running in the process to prevent a repeatedly crashing app // from blocking the user to manually clear the list. - if (app == mHomeProcess && mHomeProcess.activities.size() > 0 - && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - Iterator<ActivityRecord> it = mHomeProcess.activities.iterator(); - while (it.hasNext()) { - ActivityRecord r = it.next(); + final ArrayList<ActivityRecord> activities = app.activities; + if (mHomeProcess.contains(app) && activities.size() > 0 + && (app.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); if (r.isHomeActivity()) { Log.i(TAG, "Clearing package preferred activities from " + r.packageName); try { @@ -10230,13 +10232,20 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray)); } } - if (mHomeProcess != null && (dumpPackage == null - || mHomeProcess.pkgList.containsKey(dumpPackage))) { - if (needSep) { - pw.println(); - needSep = false; + if (!mHomeProcess.isEmpty()) { + final int size = mHomeProcess.size(); + ProcessRecord[] processes = new ProcessRecord[size]; + mHomeProcess.toArray(processes); + for (int processNdx = 0; processNdx < size; ++processNdx) { + final ProcessRecord app = processes[processNdx]; + if (dumpPackage == null || app.pkgList.containsKey(dumpPackage)) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mHomeProcess[" + processNdx + "]: " + app); + } } - pw.println(" mHomeProcess: " + mHomeProcess); } if (mPreviousProcess != null && (dumpPackage == null || mPreviousProcess.pkgList.containsKey(dumpPackage))) { @@ -11737,10 +11746,10 @@ public final class ActivityManagerService extends ActivityManagerNative } return inLaunching; } - + /** * Main code for cleaning up a process when it has gone away. This is - * called both as a result of the process dying, or directly when stopping + * called both as a result of the process dying, or directly when stopping * a process when running in single process mode. */ private final void cleanUpApplicationRecordLocked(ProcessRecord app, @@ -11751,7 +11760,7 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessesToGc.remove(app); mPendingPssProcesses.remove(app); - + // Dismiss any open dialogs. if (app.crashDialog != null && !app.forceCrashReport) { app.crashDialog.dismiss(); @@ -11768,7 +11777,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.crashing = false; app.notResponding = false; - + app.resetPackageList(mProcessStats); app.unlinkDeathRecipient(); app.thread = null; @@ -11801,7 +11810,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (checkAppInLaunchingProvidersLocked(app, false)) { restart = true; } - + // Unregister from connected content providers. if (!app.conProviders.isEmpty()) { for (int i=0; i<app.conProviders.size(); i++) { @@ -11828,7 +11837,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + skipCurrentReceiverLocked(app); // Unregister any receivers. @@ -11859,6 +11868,8 @@ public final class ActivityManagerService extends ActivityManagerNative } mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget(); + mHomeProcess.remove(app); + // If the caller is restarting this app, then leave it in its // current lists and let the caller take care of it. if (restarting) { @@ -11888,8 +11899,8 @@ public final class ActivityManagerService extends ActivityManagerNative "Clean-up removing on hold: " + app); mProcessesOnHold.remove(app); - if (app == mHomeProcess) { - mHomeProcess = null; + if (mHomeProcess.contains(app)) { + mHomeProcess.remove(app); } if (app == mPreviousProcess) { mPreviousProcess = null; @@ -13815,7 +13826,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (app == mHomeProcess) { + if (mHomeProcess.contains(app)) { if (adj > ProcessList.HOME_APP_ADJ) { // This process is hosting what we currently consider to be the // home app, so we don't want to let it go into the background. @@ -13882,7 +13893,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (procState > ActivityManager.PROCESS_STATE_SERVICE) { procState = ActivityManager.PROCESS_STATE_SERVICE; } - if (app.hasShownUi && app != mHomeProcess) { + if (app.hasShownUi && !mHomeProcess.contains(app)) { // If this process has shown some UI, let it immediately // go to the LRU list because it may be pretty heavy with // UI stuff. We'll tag it with a label just to help @@ -13945,7 +13956,7 @@ public final class ActivityManagerService extends ActivityManagerNative if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { // Not doing bind OOM management, so treat // this guy more like a started service. - if (app.hasShownUi && app != mHomeProcess) { + if (app.hasShownUi && !mHomeProcess.contains(app)) { // If this process has shown some UI, let it immediately // go to the LRU list because it may be pretty heavy with // UI stuff. We'll tag it with a label just to help @@ -14000,7 +14011,7 @@ public final class ActivityManagerService extends ActivityManagerNative // about letting this process get into the LRU // list to be killed and restarted if needed for // memory. - if (app.hasShownUi && app != mHomeProcess + if (app.hasShownUi && !mHomeProcess.contains(app) && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { adjType = "cch-bound-ui-services"; } else { @@ -14114,7 +14125,7 @@ public final class ActivityManagerService extends ActivityManagerNative clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; } if (adj > clientAdj) { - if (app.hasShownUi && app != mHomeProcess + if (app.hasShownUi && !mHomeProcess.contains(app) && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { app.adjType = "cch-ui-provider"; } else { @@ -14974,7 +14985,7 @@ public final class ActivityManagerService extends ActivityManagerNative // to be good enough at this point that destroying // activities causes more harm than good. if (curLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE - && app != mHomeProcess && app != mPreviousProcess) { + && !mHomeProcess.contains(app) && app != mPreviousProcess) { // Need to do this on its own message because the stack may not // be in a consistent state at this point. // For these apps we will also finish their activities diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index a0bbfad..2fefadf 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -2246,7 +2246,7 @@ final class ActivityStack { if (r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { - if (!r.isHomeActivity() || mService.mHomeProcess != r.app) { + if (!r.isHomeActivity() || !mService.mHomeProcess.contains(r.app)) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false); diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java index 0808861..250ab4a 100644 --- a/services/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/java/com/android/server/am/ActivityStackSupervisor.java @@ -905,7 +905,7 @@ public final class ActivityStackSupervisor { r.task.taskId, r.shortComponentName); } if (r.isHomeActivity() && r.isNotResolverActivity()) { - mService.mHomeProcess = app; + mService.mHomeProcess.add(app); } mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); r.sleeping = false; @@ -1946,7 +1946,7 @@ public final class ActivityStackSupervisor { // makes sense to. if (r.app != null && fgApp != null && r.app != fgApp && r.lastVisibleTime > mService.mPreviousProcessVisibleTime - && r.app != mService.mHomeProcess) { + && !mService.mHomeProcess.contains(r.app)) { mService.mPreviousProcess = r.app; mService.mPreviousProcessVisibleTime = r.lastVisibleTime; } diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java index ee5b890..a6b69a2 100644 --- a/services/java/com/android/server/content/SyncManager.java +++ b/services/java/com/android/server/content/SyncManager.java @@ -2889,11 +2889,12 @@ public class SyncManager { // determine if we need to set or cancel the alarm boolean shouldSet = false; boolean shouldCancel = false; - final boolean alarmIsActive = mAlarmScheduleTime != null; + final boolean alarmIsActive = (mAlarmScheduleTime != null) && (now < mAlarmScheduleTime); final boolean needAlarm = alarmTime != Long.MAX_VALUE; if (needAlarm) { - // Need the alarm if it's currently not set, or if our time is before the currently - // set time. + // Need the alarm if + // - it's currently not set + // - if the alarm is set in the past. if (!alarmIsActive || alarmTime < mAlarmScheduleTime) { shouldSet = true; } @@ -2910,7 +2911,7 @@ public class SyncManager { + " secs from now"); } mAlarmScheduleTime = alarmTime; - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, + mAlarmService.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mSyncAlarmIntent); } else if (shouldCancel) { mAlarmScheduleTime = null; diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java index 25529a6..e3693f8 100644 --- a/services/java/com/android/server/content/SyncStorageEngine.java +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -53,6 +53,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; @@ -71,7 +72,7 @@ public class SyncStorageEngine extends Handler { private static final String TAG = "SyncManager"; private static final boolean DEBUG = true; - private static final boolean DEBUG_FILE = true; + private static final String TAG_FILE = "SyncManagerFile"; private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId"; private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles"; @@ -420,9 +421,12 @@ public class SyncStorageEngine extends Handler { File systemDir = new File(dataDir, "system"); File syncDir = new File(systemDir, "sync"); syncDir.mkdirs(); + + maybeDeleteLegacyPendingInfoLocked(syncDir); + mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); - mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); + mPendingFile = new AtomicFile(new File(syncDir, "pending.xml")); mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); readAccountInfoLocked(); @@ -676,7 +680,8 @@ public class SyncStorageEngine extends Handler { continue; } for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { - if (providerName != null && !providerName.equals(authorityInfo.authority)) { + if (providerName != null + && !providerName.equals(authorityInfo.authority)) { continue; } if (authorityInfo.backoffTime != nextSyncTime @@ -774,10 +779,12 @@ public class SyncStorageEngine extends Handler { } synchronized (mAuthorities) { if (toUpdate.period <= 0 && add) { - Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-" + add); + Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-" + + add); } if (toUpdate.extras == null) { - Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-" + add); + Log.e(TAG, "null extras, should never happen in updateOrRemovePeriodicSync: add-" + + add); } try { AuthorityInfo authority = @@ -806,7 +813,7 @@ public class SyncStorageEngine extends Handler { if (!alreadyPresent) { authority.periodicSyncs.add(new PeriodicSync(toUpdate)); SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); - status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0); + status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0L); } } else { // Remove any periodic syncs that match the authority and extras. @@ -824,7 +831,8 @@ public class SyncStorageEngine extends Handler { if (status != null) { status.removePeriodicSyncTime(i); } else { - Log.e(TAG, "Tried removing sync status on remove periodic sync but did not find it."); + Log.e(TAG, "Tried removing sync status on remove periodic sync but" + + "did not find it."); } } else { i++; @@ -942,7 +950,7 @@ public class SyncStorageEngine extends Handler { op = new PendingOperation(op); op.authorityId = authority.ident; mPendingOperations.add(op); - writePendingOperationsLocked(); + appendPendingOperationLocked(op); SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); status.pending = true; @@ -1660,7 +1668,9 @@ public class SyncStorageEngine extends Handler { FileInputStream fis = null; try { fis = mAccountInfoFile.openRead(); - if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); + } XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); int eventType = parser.getEventType(); @@ -1745,6 +1755,20 @@ public class SyncStorageEngine extends Handler { } /** + * Ensure the old pending.bin is deleted, as it has been changed to pending.xml. + * pending.xml was used starting in KLP. + * @param syncDir directory where the sync files are located. + */ + private void maybeDeleteLegacyPendingInfoLocked(File syncDir) { + File file = new File(syncDir, "pending.bin"); + if (!file.exists()) { + return; + } else { + file.delete(); + } + } + + /** * some authority names have changed. copy over their settings and delete the old ones * @return true if a change was made */ @@ -1832,18 +1856,21 @@ public class SyncStorageEngine extends Handler { syncable = "unknown"; } authority = mAuthorities.get(id); - if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" - + accountName + " auth=" + authorityName - + " user=" + userId - + " enabled=" + enabled - + " syncable=" + syncable); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Adding authority: account=" + + accountName + " auth=" + authorityName + + " user=" + userId + + " enabled=" + enabled + + " syncable=" + syncable); + } if (authority == null) { - if (DEBUG_FILE) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { Log.v(TAG, "Creating entry"); } if (accountName != null && accountType != null) { authority = getOrCreateAuthorityLocked( - new Account(accountName, accountType), userId, authorityName, id, false); + new Account(accountName, accountType), userId, authorityName, id, + false); } else { authority = getOrCreateAuthorityLocked( new ComponentName(packageName, className), userId, id, false); @@ -1943,7 +1970,9 @@ public class SyncStorageEngine extends Handler { * Write all account information to the account file. */ private void writeAccountInfoLocked() { - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); + } FileOutputStream fos = null; try { @@ -2041,7 +2070,9 @@ public class SyncStorageEngine extends Handler { final boolean hasType = db.getVersion() >= 11; // Copy in all of the status information, as well as accounts. - if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Reading legacy sync accounts db"); + } SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables("stats, status"); HashMap<String,String> map = new HashMap<String,String>(); @@ -2151,7 +2182,9 @@ public class SyncStorageEngine extends Handler { * Read all sync status back in to the initial engine state. */ private void readStatusLocked() { - if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); + } try { byte[] data = mStatusFile.readFully(); Parcel in = Parcel.obtain(); @@ -2163,8 +2196,10 @@ public class SyncStorageEngine extends Handler { SyncStatusInfo status = new SyncStatusInfo(in); if (mAuthorities.indexOfKey(status.authorityId) >= 0) { status.pending = false; - if (DEBUG_FILE) Log.v(TAG, "Adding status for id " - + status.authorityId); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Adding status for id " + + status.authorityId); + } mSyncStatus.put(status.authorityId, status); } } else { @@ -2182,7 +2217,9 @@ public class SyncStorageEngine extends Handler { * Write all sync status to the sync status file. */ private void writeStatusLocked() { - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); + } // The file is being written, so we don't need to have a scheduled // write until the next change. @@ -2211,103 +2248,97 @@ public class SyncStorageEngine extends Handler { } } - public static final int PENDING_OPERATION_VERSION = 4; + public static final int PENDING_OPERATION_VERSION = 3; - /** - * Read all pending operations back in to the initial engine state. - */ + /** Read all pending operations back in to the initial engine state. */ private void readPendingOperationsLocked() { - if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile()); - try { - readPendingAsXml(); - } catch (XmlPullParserException e) { - Log.d(TAG, "Error parsing pending as xml, trying as parcel."); - try { - readPendingAsParcelled(); - } catch (java.io.IOException e1) { - Log.i(TAG, "No initial pending operations"); + FileInputStream fis = null; + if (!mPendingFile.getBaseFile().exists()) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "No pending operation file."); + return; } } - } - - private void readPendingAsXml() throws XmlPullParserException { - FileInputStream fis = null; try { fis = mPendingFile.openRead(); - XmlPullParser parser = Xml.newPullParser(); + XmlPullParser parser; + parser = Xml.newPullParser(); parser.setInput(fis, null); + int eventType = parser.getEventType(); while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { eventType = parser.next(); } - if (eventType == XmlPullParser.END_DOCUMENT) return; + if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read. String tagName = parser.getName(); - if ("pending".equals(tagName)) { - int version = -1; - String versionString = parser.getAttributeValue(null, "version"); - if (versionString == null || - Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) { - Log.w(TAG, "Unknown pending operation version " - + version + "; trying to read as binary."); - throw new XmlPullParserException("Unknown version."); - } - eventType = parser.next(); + do { PendingOperation pop = null; - do { - if (eventType == XmlPullParser.START_TAG) { - try { - tagName = parser.getName(); - if (parser.getDepth() == 2 && "op".equals(tagName)) { - int authorityId = Integer.valueOf(parser.getAttributeValue( - null, XML_ATTR_AUTHORITYID)); - boolean expedited = Boolean.valueOf(parser.getAttributeValue( - null, XML_ATTR_EXPEDITED)); - int syncSource = Integer.valueOf(parser.getAttributeValue( - null, XML_ATTR_SOURCE)); - int reason = Integer.valueOf(parser.getAttributeValue( - null, XML_ATTR_REASON)); - AuthorityInfo authority = mAuthorities.get(authorityId); - if (DEBUG_FILE) { - Log.v(TAG, authorityId + " " + expedited + " " + syncSource + " " + reason); - } - if (authority != null) { - pop = new PendingOperation( - authority.account, authority.userId, reason, syncSource, - authority.authority, new Bundle(), expedited); - pop.authorityId = authorityId; - pop.flatExtras = null; // No longer used. - mPendingOperations.add(pop); - if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + pop.account - + " auth=" + pop.authority + if (eventType == XmlPullParser.START_TAG) { + try { + tagName = parser.getName(); + if (parser.getDepth() == 1 && "op".equals(tagName)) { + // Verify version. + String versionString = + parser.getAttributeValue(null, XML_ATTR_VERSION); + if (versionString == null || + Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) { + Log.w(TAG, "Unknown pending operation version " + versionString); + throw new java.io.IOException("Unknown version."); + } + int authorityId = Integer.valueOf(parser.getAttributeValue( + null, XML_ATTR_AUTHORITYID)); + boolean expedited = Boolean.valueOf(parser.getAttributeValue( + null, XML_ATTR_EXPEDITED)); + int syncSource = Integer.valueOf(parser.getAttributeValue( + null, XML_ATTR_SOURCE)); + int reason = Integer.valueOf(parser.getAttributeValue( + null, XML_ATTR_REASON)); + AuthorityInfo authority = mAuthorities.get(authorityId); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " " + + reason); + } + if (authority != null) { + pop = new PendingOperation( + authority.account, authority.userId, reason, + syncSource, authority.authority, new Bundle(), + expedited); + pop.flatExtras = null; // No longer used. + mPendingOperations.add(pop); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "Adding pending op: " + + pop.authority + " src=" + pop.syncSource + " reason=" + pop.reason + " expedited=" + pop.expedited); - } else { - // Skip non-existent authority; - pop = null; - if (DEBUG_FILE) { - Log.v(TAG, "No authority found for " + authorityId - + ", skipping"); - } } - } else if (parser.getDepth() == 3 && - pop != null && - "extra".equals(tagName)) { - parseExtra(parser, pop.extras); + } else { + // Skip non-existent authority. + pop = null; + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "No authority found for " + authorityId + + ", skipping"); + } } - } catch (NumberFormatException e) { - Log.d(TAG, "Invalid data in xml file.", e); + } else if (parser.getDepth() == 2 && + pop != null && + "extra".equals(tagName)) { + parseExtra(parser, pop.extras); } + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid data in xml file.", e); } - eventType = parser.next(); - } while(eventType != XmlPullParser.END_DOCUMENT); - } + } + eventType = parser.next(); + } while(eventType != XmlPullParser.END_DOCUMENT); } catch (java.io.IOException e) { - if (fis == null) Log.i(TAG, "No initial pending operations."); - else Log.w(TAG, "Error reading pending data.", e); - return; + Log.w(TAG_FILE, "Error reading pending data.", e); + } catch (XmlPullParserException e) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.w(TAG_FILE, "Error parsing pending ops xml.", e); + } } finally { if (fis != null) { try { @@ -2316,60 +2347,99 @@ public class SyncStorageEngine extends Handler { } } } + + private static final String XML_ATTR_AUTHORITYID = "authority_id"; + private static final String XML_ATTR_SOURCE = "source"; + private static final String XML_ATTR_EXPEDITED = "expedited"; + private static final String XML_ATTR_REASON = "reason"; + private static final String XML_ATTR_VERSION = "version"; + /** - * Old format of reading pending.bin as a parcelled file. Replaced in lieu of JSON because - * persisting parcels is unsafe. - * @throws java.io.IOException + * Write all currently pending ops to the pending ops file. */ - private void readPendingAsParcelled() throws java.io.IOException { - byte[] data = mPendingFile.readFully(); - Parcel in = Parcel.obtain(); - in.unmarshall(data, 0, data.length); - in.setDataPosition(0); - final int SIZE = in.dataSize(); - while (in.dataPosition() < SIZE) { - int version = in.readInt(); - if (version != 3 && version != 1) { - Log.w(TAG, "Unknown pending operation version " - + version + "; dropping all ops"); - break; - } - int authorityId = in.readInt(); - int syncSource = in.readInt(); - byte[] flatExtras = in.createByteArray(); - boolean expedited; - if (version == PENDING_OPERATION_VERSION) { - expedited = in.readInt() != 0; - } else { - expedited = false; - } - int reason = in.readInt(); - AuthorityInfo authority = mAuthorities.get(authorityId); - if (authority != null) { - Bundle extras; - if (flatExtras != null) { - extras = unflattenBundle(flatExtras); - } else { - // if we are unable to parse the extras for whatever reason convert this - // to a regular sync by creating an empty extras - extras = new Bundle(); + private void writePendingOperationsLocked() { + final int N = mPendingOperations.size(); + FileOutputStream fos = null; + try { + if (N == 0) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "Truncating " + mPendingFile.getBaseFile()); } - PendingOperation op = new PendingOperation( - authority.account, authority.userId, reason, syncSource, - authority.authority, extras, expedited); - op.authorityId = authorityId; - op.flatExtras = flatExtras; - if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account - + " auth=" + op.authority - + " src=" + op.syncSource - + " reason=" + op.reason - + " expedited=" + op.expedited - + " extras=" + op.extras); - mPendingOperations.add(op); + mPendingFile.truncate(); + return; + } + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "Writing new " + mPendingFile.getBaseFile()); + } + fos = mPendingFile.startWrite(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + + for (int i = 0; i < N; i++) { + PendingOperation pop = mPendingOperations.get(i); + writePendingOperationLocked(pop, out); + } + out.endDocument(); + mPendingFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing pending operations", e1); + if (fos != null) { + mPendingFile.failWrite(fos); } } } + /** Write all currently pending ops to the pending ops file. */ + private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out) + throws IOException { + // Pending operation. + out.startTag(null, "op"); + + out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION)); + out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId)); + out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource)); + out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited)); + out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason)); + extrasToXml(out, pop.extras); + + out.endTag(null, "op"); + } + + /** + * Append the given operation to the pending ops file; if unable to, + * write all pending ops. + */ + private void appendPendingOperationLocked(PendingOperation op) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); + } + FileOutputStream fos = null; + try { + fos = mPendingFile.openAppend(); + } catch (java.io.IOException e) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Failed append; writing full file"); + } + writePendingOperationsLocked(); + return; + } + + try { + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + writePendingOperationLocked(op, out); + out.endDocument(); + mPendingFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing appending operation", e1); + mPendingFile.failWrite(fos); + } finally { + try { + fos.close(); + } catch (IOException e) {} + } + } + static private byte[] flattenBundle(Bundle bundle) { byte[] flatData = null; Parcel parcel = Parcel.obtain(); @@ -2399,54 +2469,6 @@ public class SyncStorageEngine extends Handler { return bundle; } - private static final String XML_ATTR_AUTHORITYID = "authority_id"; - private static final String XML_ATTR_SOURCE = "source"; - private static final String XML_ATTR_EXPEDITED = "expedited"; - private static final String XML_ATTR_REASON = "reason"; - /** - * Write all currently pending ops to the pending ops file. TODO: Change this from xml - * so that we can append to this file as before. - */ - private void writePendingOperationsLocked() { - final int N = mPendingOperations.size(); - FileOutputStream fos = null; - try { - if (N == 0) { - if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); - mPendingFile.truncate(); - return; - } - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); - fos = mPendingFile.startWrite(); - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(fos, "utf-8"); - out.startDocument(null, true); - out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - out.startTag(null, "pending"); - out.attribute(null, "version", Integer.toString(PENDING_OPERATION_VERSION)); - - for (int i = 0; i < N; i++) { - PendingOperation pop = mPendingOperations.get(i); - out.startTag(null, "op"); - out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId)); - out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource)); - out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited)); - out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason)); - extrasToXml(out, pop.extras); - out.endTag(null, "op"); - } - out.endTag(null, "pending"); - out.endDocument(); - mPendingFile.finishWrite(fos); - } catch (java.io.IOException e1) { - Log.w(TAG, "Error writing pending operations", e1); - if (fos != null) { - mPendingFile.failWrite(fos); - } - } - } - private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException { for (String key : extras.keySet()) { out.startTag(null, "extra"); @@ -2479,35 +2501,6 @@ public class SyncStorageEngine extends Handler { } } -// /** -// * Update the pending ops file, if e -// */ -// private void appendPendingOperationLocked(PendingOperation op) { -// if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); -// FileOutputStream fos = null; -// try { -// fos = mPendingFile.openAppend(); -// } catch (java.io.IOException e) { -// if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file"); -// writePendingOperationsLocked(); -// return; -// } -// -// try { -// Parcel out = Parcel.obtain(); -// writePendingOperationLocked(op, out); -// fos.write(out.marshall()); -// out.recycle(); -// } catch (java.io.IOException e1) { -// Log.w(TAG, "Error writing pending operations", e1); -// } finally { -// try { -// fos.close(); -// } catch (java.io.IOException e2) { -// } -// } -// } - private void requestSync(Account account, int userId, int reason, String authority, Bundle extras) { // If this is happening in the system process, then call the syncrequest listener @@ -2568,7 +2561,9 @@ public class SyncStorageEngine extends Handler { * Write all sync statistics to the sync status file. */ private void writeStatisticsLocked() { - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); + } // The file is being written, so we don't need to have a scheduled // write until the next change. @@ -2611,7 +2606,7 @@ public class SyncStorageEngine extends Handler { sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n"); for (PendingOperation pop : mPendingOperations) { sb.append("(" + pop.account) - .append(", " + pop.userId) + .append(", u" + pop.userId) .append(", " + pop.authority) .append(", " + pop.extras) .append(")\n"); diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java index db030f1..c215f40 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -345,6 +345,12 @@ public final class WifiService extends IWifiManager.Stub { return mBatchedScanSupported; } + public void pollBatchedScan() { + enforceChangePermission(); + if (mBatchedScanSupported == false) return; + mWifiStateMachine.requestBatchedScanPoll(); + } + /** * see {@link android.net.wifi.WifiManager#requestBatchedScan()} */ diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java index dff6661..e44652f 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java @@ -67,7 +67,7 @@ public class SyncStorageEngineTest extends AndroidTestCase { /** * Test that we handle the case of a history row being old enough to purge before the - * correcponding sync is finished. This can happen if the clock changes while we are syncing. + * corresponding sync is finished. This can happen if the clock changes while we are syncing. * */ // TODO: this test causes AidlTest to fail. Omit for now @@ -104,6 +104,17 @@ public class SyncStorageEngineTest extends AndroidTestCase { engine.clearAndReadState(); assert(engine.getPendingOperationCount() == 1); + List<SyncStorageEngine.PendingOperation> pops = engine.getPendingOperations(); + SyncStorageEngine.PendingOperation popRetrieved = pops.get(0); + assertEquals(pop.account, popRetrieved.account); + assertEquals(pop.reason, popRetrieved.reason); + assertEquals(pop.userId, popRetrieved.userId); + assertEquals(pop.syncSource, popRetrieved.syncSource); + assertEquals(pop.authority, popRetrieved.authority); + assertEquals(pop.expedited, popRetrieved.expedited); + assertEquals(pop.serviceName, popRetrieved.serviceName); + assert(android.content.PeriodicSync.syncExtrasEquals(pop.extras, popRetrieved.extras)); + } /** diff --git a/wifi/java/android/net/wifi/BatchedScanSettings.java b/wifi/java/android/net/wifi/BatchedScanSettings.java index 82945d6..44a2ab4 100644 --- a/wifi/java/android/net/wifi/BatchedScanSettings.java +++ b/wifi/java/android/net/wifi/BatchedScanSettings.java @@ -51,6 +51,7 @@ public class BatchedScanSettings implements Parcelable { public final static int MAX_AP_FOR_DISTANCE = MAX_AP_PER_SCAN; public final static int DEFAULT_AP_FOR_DISTANCE = 0; + public final static int MAX_WIFI_CHANNEL = 196; /** The expected number of scans per batch. Note that the firmware may drop scans * leading to fewer scans during the normal batch scan duration. This value need not @@ -113,7 +114,7 @@ public class BatchedScanSettings implements Parcelable { for (String channel : channelSet) { try { int i = Integer.parseInt(channel); - if (i > 0 && i < 197) continue; + if (i > 0 && i <= MAX_WIFI_CHANNEL) continue; } catch (NumberFormatException e) {} if (channel.equals("A") || channel.equals("B")) continue; return false; diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index c8cf323..4f68ca0 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -124,5 +124,7 @@ interface IWifiManager List<BatchedScanResult> getBatchedScanResults(String callingPackage); boolean isBatchedScanSupported(); + + void pollBatchedScan(); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 01ca378..a15b664 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -840,6 +840,32 @@ public class WifiManager { } /** + * Force a re-reading of batched scan results. This will attempt + * to read more information from the chip, but will do so at the expense + * of previous data. Rate limited to the current scan frequency. + * + * pollBatchedScan will always wait 1 period from the start of the batch + * before trying to read from the chip, so if your #scans/batch == 1 this will + * have no effect. + * + * If you had already waited 1 period before calling, this should have + * immediate (though async) effect. + * + * If you call before that 1 period is up this will set up a timer and fetch + * results when the 1 period is up. + * + * Servicing a pollBatchedScan request (immediate or after timed delay) starts a + * new batch, so if you were doing 10 scans/batch and called in the 4th scan, you + * would get data in the 4th and then again 10 scans later. + * @hide + */ + public void pollBatchedScan() { + try { + mService.pollBatchedScan(); + } catch (RemoteException e) { } + } + + /** * Return dynamic information about the current Wi-Fi connection, if any is active. * @return the Wi-Fi information, contained in {@link WifiInfo}. */ diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index f6d5c98..a80238b 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -506,13 +506,14 @@ public class WifiMonitor { Log.d(TAG, "Event [" + eventStr + "]"); } + String iface = "p2p0"; WifiMonitor m = null; mStateMachine = null; if (eventStr.startsWith("IFNAME=")) { int space = eventStr.indexOf(' '); if (space != -1) { - String iface = eventStr.substring(7,space); + iface = eventStr.substring(7,space); m = mWifiMonitorSingleton.getMonitor(iface); if (m == null && iface.startsWith("p2p-")) { // p2p interfaces are created dynamically, but we have @@ -520,20 +521,20 @@ public class WifiMonitor { // for it explicitly, and send messages there .. m = mWifiMonitorSingleton.getMonitor("p2p0"); } - if (m != null) { - if (m.mMonitoring) { - mStateMachine = m.mWifiStateMachine; - eventStr = eventStr.substring(space + 1); - } - else { - if (DBG) Log.d(TAG, "Dropping event because monitor (" + iface + - ") is stopped"); - continue; - } - } - else { - eventStr = eventStr.substring(space + 1); - } + eventStr = eventStr.substring(space + 1); + } + } else { + // events without prefix belong to p2p0 monitor + m = mWifiMonitorSingleton.getMonitor("p2p0"); + } + + if (m != null) { + if (m.mMonitoring) { + mStateMachine = m.mWifiStateMachine; + } else { + if (DBG) Log.d(TAG, "Dropping event because monitor (" + iface + + ") is stopped"); + continue; } } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 0359076..c3ed03c 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -82,8 +82,13 @@ public class WifiNative { public WifiNative(String interfaceName) { mInterfaceName = interfaceName; - mInterfacePrefix = "IFNAME=" + interfaceName + " "; mTAG = "WifiNative-" + interfaceName; + if (!interfaceName.equals("p2p0")) { + mInterfacePrefix = "IFNAME=" + interfaceName + " "; + } else { + // commands for p2p0 interface don't need prefix + mInterfacePrefix = ""; + } } public boolean connectToSupplicant() { @@ -221,8 +226,9 @@ public class WifiNative { /** * Format of command - * DRIVER WLS_BATCHING SET SCAN_FRQ=x BESTN=y CHANNEL=<z, w, t> RTT=s + * DRIVER WLS_BATCHING SET SCAN_FRQ=x MSCAN=r BESTN=y CHANNEL=<z, w, t> RTT=s * where x is an ascii representation of an integer number of seconds between scans + * r is an ascii representation of an integer number of scans per batch * y is an ascii representation of an integer number of the max AP to remember per scan * z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values * indicating entire ranges of channels @@ -235,8 +241,9 @@ public class WifiNative { public String setBatchedScanSettings(BatchedScanSettings settings) { if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP"); String cmd = "DRIVER WLS_BATCHING SET SCAN_FRQ=" + settings.scanIntervalSec; + cmd += " MSCAN=" + settings.maxScansPerBatch; if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) { - cmd += " BESTN " + settings.maxApPerScan; + cmd += " BESTN=" + settings.maxApPerScan; } if (settings.channelSet != null && !settings.channelSet.isEmpty()) { cmd += " CHANNEL=<"; diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 764c00a..8b7b8ae 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -127,6 +127,8 @@ public class WifiStateMachine extends StateMachine { private final List<BatchedScanResult> mBatchedScanResults = new ArrayList<BatchedScanResult>(); private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE; + private int mExpectedBatchedScans = 0; + private long mBatchedScanMinPollTime = 0; /* Chipset supports background scan */ private final boolean mBackgroundScanSupported; @@ -366,8 +368,9 @@ public class WifiStateMachine extends StateMachine { * arg1 = responsible UID * obj = the new settings */ - public static final int CMD_SET_BATCH_SCAN = BASE + 135; + public static final int CMD_SET_BATCHED_SCAN = BASE + 135; public static final int CMD_START_NEXT_BATCHED_SCAN = BASE + 136; + public static final int CMD_POLL_BATCHED_SCAN = BASE + 137; public static final int CONNECT_MODE = 1; public static final int SCAN_ONLY_MODE = 2; @@ -766,7 +769,7 @@ public class WifiStateMachine extends StateMachine { * start or stop batched scanning using the given settings */ public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) { - sendMessage(CMD_SET_BATCH_SCAN, callingUid, 0, settings); + sendMessage(CMD_SET_BATCHED_SCAN, callingUid, 0, settings); } public List<BatchedScanResult> syncGetBatchedScanResultsList() { @@ -780,6 +783,10 @@ public class WifiStateMachine extends StateMachine { } } + public void requestBatchedScanPoll() { + sendMessage(CMD_POLL_BATCHED_SCAN); + } + private void startBatchedScan() { // first grab any existing data retrieveBatchedScanData(); @@ -789,8 +796,8 @@ public class WifiStateMachine extends StateMachine { String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings); try { - int expected = Integer.parseInt(scansExpected); - setNextBatchedAlarm(expected); + mExpectedBatchedScans = Integer.parseInt(scansExpected); + setNextBatchedAlarm(mExpectedBatchedScans); } catch (NumberFormatException e) { loge("Exception parsing WifiNative.setBatchedScanSettings response " + e); } @@ -803,9 +810,27 @@ public class WifiStateMachine extends StateMachine { private void startNextBatchedScan() { // first grab any existing data - int nextCount = retrieveBatchedScanData(); + retrieveBatchedScanData(); - setNextBatchedAlarm(nextCount); + setNextBatchedAlarm(mExpectedBatchedScans); + } + + private void handleBatchedScanPollRequest() { + // if there is no appropriate PollTime that's because we either aren't + // batching or we've already set a time for a poll request + if (mBatchedScanMinPollTime == 0) return; + if (mBatchedScanSettings == null) return; + + long now = System.currentTimeMillis(); + + if (now > mBatchedScanMinPollTime) { + // do the poll and reset our timers + startNextBatchedScan(); + } else { + mAlarmManager.set(AlarmManager.RTC_WAKEUP, mBatchedScanMinPollTime, + mBatchedScanIntervalIntent); + mBatchedScanMinPollTime = 0; + } } // return true if new/different @@ -832,6 +857,9 @@ public class WifiStateMachine extends StateMachine { if (mBatchedScanSettings == null || scansExpected < 1) return; + mBatchedScanMinPollTime = System.currentTimeMillis() + + mBatchedScanSettings.scanIntervalSec * 1000; + if (mBatchedScanSettings.maxScansPerBatch < scansExpected) { scansExpected = mBatchedScanSettings.maxScansPerBatch; } @@ -876,22 +904,18 @@ public class WifiStateMachine extends StateMachine { * etc * "----" */ - private int retrieveBatchedScanData() { + private void retrieveBatchedScanData() { String rawData = mWifiNative.getBatchedScanResults(); + mBatchedScanMinPollTime = 0; if (rawData == null) { loge("Unexpected null BatchedScanResults"); - return 0; + return; } - int nextCount = 0; int scanCount = 0; - final String END_OF_SCAN = "===="; - final String END_OF_BATCH = "%%%%"; final String END_OF_BATCHES = "----"; final String SCANCOUNT = "scancount="; - final String NEXTCOUNT = "nextcount="; final String TRUNCATED = "trunc"; - final String APCOUNT = "apcount="; final String AGE = "age="; final String DIST = "dist="; final String DISTSD = "distsd="; @@ -905,16 +929,7 @@ public class WifiStateMachine extends StateMachine { } if (scanCount == 0) { loge("scanCount not found"); - return 0; - } - if (splitData[n].startsWith(NEXTCOUNT)) { - try { - nextCount = Integer.parseInt(splitData[n++].substring(NEXTCOUNT.length())); - } catch (NumberFormatException e) {} - } - if (nextCount == 0) { - loge("nextCount not found"); - return 0; + return; } final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION); @@ -942,9 +957,9 @@ public class WifiStateMachine extends StateMachine { if (mBatchedScanResults.size() > 0) { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - return nextCount; + return; } - if ((splitData[n].equals(END_OF_SCAN)) || splitData[n].equals(END_OF_BATCH)) { + if ((splitData[n].equals(END_STR)) || splitData[n].equals(DELIMITER_STR)) { if (bssid != null) { batchedScanResult.scanResults.add(new ScanResult( wifiSsid, bssid, "", level, freq, tsf, dist, distSd)); @@ -955,7 +970,7 @@ public class WifiStateMachine extends StateMachine { tsf = 0; dist = distSd = ScanResult.UNSPECIFIED; } - if (splitData[n].equals(END_OF_BATCH)) { + if (splitData[n].equals(END_STR)) { if (batchedScanResult.scanResults.size() != 0) { mBatchedScanResults.add(batchedScanResult); batchedScanResult = new BatchedScanResult(); @@ -1010,7 +1025,7 @@ public class WifiStateMachine extends StateMachine { rawData = mWifiNative.getBatchedScanResults(); if (rawData == null) { loge("Unexpected null BatchedScanResults"); - return nextCount; + return; } splitData = rawData.split("\n"); if (splitData.length == 0 || splitData[0].equals("ok")) { @@ -1018,7 +1033,7 @@ public class WifiStateMachine extends StateMachine { if (mBatchedScanResults.size() > 0) { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - return nextCount; + return; } n = 0; } @@ -2266,9 +2281,11 @@ public class WifiStateMachine extends StateMachine { sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode); } break; - case CMD_SET_BATCH_SCAN: + case CMD_SET_BATCHED_SCAN: recordBatchedScanSettings((BatchedScanSettings)message.obj); break; + case CMD_POLL_BATCHED_SCAN: + handleBatchedScanPollRequest(); case CMD_START_NEXT_BATCHED_SCAN: startNextBatchedScan(); break; @@ -2808,7 +2825,7 @@ public class WifiStateMachine extends StateMachine { noteScanStart(message.arg1, (WorkSource) message.obj); startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP); break; - case CMD_SET_BATCH_SCAN: + case CMD_SET_BATCHED_SCAN: recordBatchedScanSettings((BatchedScanSettings)message.obj); startBatchedScan(); break; |