diff options
38 files changed, 1207 insertions, 256 deletions
diff --git a/api/current.txt b/api/current.txt index 5346895..b307d61 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13774,6 +13774,7 @@ package android.hardware.fingerprint { field public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; // 0x3e8 field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 @@ -17809,6 +17810,7 @@ package android.media.tv { method public final int getType(); method public final float getVideoFrameRate(); method public final int getVideoHeight(); + method public final float getVideoPixelAspectRatio(); method public final int getVideoWidth(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR; @@ -17827,6 +17829,7 @@ package android.media.tv { method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int); + method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); method public final android.media.tv.TvTrackInfo.Builder setVideoWidth(int); } @@ -18203,6 +18206,7 @@ package android.net { method public int describeContents(); method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; method public java.net.InetAddress getByName(java.lang.String) throws java.net.UnknownHostException; + method public long getNetworkHandle(); method public javax.net.SocketFactory getSocketFactory(); method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; @@ -39806,6 +39810,7 @@ package android.widget { method public boolean getOverlapAnchor(); method public int getSoftInputMode(); method public int getWidth(); + method public int getWindowLayoutType(); method public boolean isAboveAnchor(); method public boolean isAttachedInDecor(); method public boolean isClippingEnabled(); @@ -39835,6 +39840,7 @@ package android.widget { method public void setTouchable(boolean); method public void setWidth(int); method public deprecated void setWindowLayoutMode(int, int); + method public void setWindowLayoutType(int); method public void showAsDropDown(android.view.View); method public void showAsDropDown(android.view.View, int, int); method public void showAsDropDown(android.view.View, int, int, int); diff --git a/api/system-current.txt b/api/system-current.txt index c7db72b..9184d8b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -14070,6 +14070,7 @@ package android.hardware.fingerprint { field public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; // 0x3e8 field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 @@ -19260,6 +19261,7 @@ package android.media.tv { method public final int getType(); method public final float getVideoFrameRate(); method public final int getVideoHeight(); + method public final float getVideoPixelAspectRatio(); method public final int getVideoWidth(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR; @@ -19278,6 +19280,7 @@ package android.media.tv { method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int); + method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); method public final android.media.tv.TvTrackInfo.Builder setVideoWidth(int); } @@ -19661,6 +19664,7 @@ package android.net { method public int describeContents(); method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException; method public java.net.InetAddress getByName(java.lang.String) throws java.net.UnknownHostException; + method public long getNetworkHandle(); method public javax.net.SocketFactory getSocketFactory(); method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; @@ -42731,6 +42735,7 @@ package android.widget { method public boolean getOverlapAnchor(); method public int getSoftInputMode(); method public int getWidth(); + method public int getWindowLayoutType(); method public boolean isAboveAnchor(); method public boolean isAttachedInDecor(); method public boolean isClippingEnabled(); @@ -42760,6 +42765,7 @@ package android.widget { method public void setTouchable(boolean); method public void setWidth(int); method public deprecated void setWindowLayoutMode(int, int); + method public void setWindowLayoutType(int); method public void showAsDropDown(android.view.View); method public void showAsDropDown(android.view.View, int, int); method public void showAsDropDown(android.view.View, int, int, int); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 5f85e7d..b757a9a 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -56,7 +56,7 @@ public class FingerprintManager { private static final boolean DEBUG = true; private static final int MSG_ENROLL_RESULT = 100; private static final int MSG_ACQUIRED = 101; - private static final int MSG_PROCESSED = 102; + private static final int MSG_AUTHENTICATED = 102; private static final int MSG_ERROR = 103; private static final int MSG_REMOVED = 104; @@ -103,6 +103,11 @@ public class FingerprintManager { */ public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + /** + * The operation was canceled because the API is locked out due to too many attempts. + */ + public static final int FINGERPRINT_ERROR_LOCKOUT = 7; + /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. @@ -169,15 +174,9 @@ public class FingerprintManager { private Fingerprint mRemovalFingerprint; private class OnEnrollCancelListener implements OnCancelListener { - private long mChallenge; - - public OnEnrollCancelListener(long challenge) { - mChallenge = challenge; - } - @Override public void onCancel() { - cancelEnrollment(mChallenge); + cancelEnrollment(); } } @@ -437,14 +436,14 @@ public class FingerprintManager { * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at * which point the object is no longer valid. The operation can be canceled by using the * provided cancel object. - * @param challenge a unique id provided by a recent verification of device credentials - * (e.g. pin, pattern or password). + * @param token a unique token provided by a recent creation or verification of device + * credentials (e.g. pin, pattern or password). * @param cancel an object that can be used to cancel enrollment * @param callback an object to receive enrollment events * @param flags optional flags * @hide */ - public void enroll(long challenge, CancellationSignal cancel, EnrollmentCallback callback, + public void enroll(byte [] token, CancellationSignal cancel, EnrollmentCallback callback, int flags) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); @@ -455,13 +454,13 @@ public class FingerprintManager { Log.w(TAG, "enrollment already canceled"); return; } else { - cancel.setOnCancelListener(new OnEnrollCancelListener(challenge)); + cancel.setOnCancelListener(new OnEnrollCancelListener()); } } if (mService != null) try { mEnrollmentCallback = callback; - mService.enroll(mToken, challenge, getCurrentUserId(), mServiceReceiver, flags); + mService.enroll(mToken, token, getCurrentUserId(), mServiceReceiver, flags); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); if (callback != null) { @@ -574,8 +573,8 @@ public class FingerprintManager { case MSG_ACQUIRED: sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */); break; - case MSG_PROCESSED: - sendProcessedResult((Fingerprint) msg.obj); + case MSG_AUTHENTICATED: + sendAuthenticatedResult((Fingerprint) msg.obj); break; case MSG_ERROR: sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */); @@ -617,7 +616,7 @@ public class FingerprintManager { } } - private void sendProcessedResult(Fingerprint fp) { + private void sendAuthenticatedResult(Fingerprint fp) { if (mAuthenticationCallback != null) { if (fp.getFingerId() == 0 && fp.getGroupId() == 0) { // Fingerprint template valid but doesn't match one in database @@ -667,7 +666,7 @@ public class FingerprintManager { mRemovalCallback = null; } - private void cancelEnrollment(long challenge) { + private void cancelEnrollment() { if (mService != null) try { mService.cancelEnrollment(mToken); } catch (RemoteException e) { @@ -695,8 +694,11 @@ public class FingerprintManager { return mContext.getString( com.android.internal.R.string.fingerprint_error_no_space); case FINGERPRINT_ERROR_TIMEOUT: - return mContext.getString( - com.android.internal.R.string.fingerprint_error_timeout); + return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout); + case FINGERPRINT_ERROR_CANCELED: + return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled); + case FINGERPRINT_ERROR_LOCKOUT: + return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout); default: if (errMsg >= FINGERPRINT_ERROR_VENDOR_BASE) { int msgNumber = errMsg - FINGERPRINT_ERROR_VENDOR_BASE; @@ -753,8 +755,8 @@ public class FingerprintManager { mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0, deviceId).sendToTarget(); } - public void onProcessed(long deviceId, int fingerId, int groupId) { - mHandler.obtainMessage(MSG_PROCESSED, + public void onAuthenticated(long deviceId, int fingerId, int groupId) { + mHandler.obtainMessage(MSG_AUTHENTICATED, new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 2fcb20e..6fe72d5 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -33,7 +33,7 @@ interface IFingerprintService { void cancelAuthentication(IBinder token); // Start fingerprint enrollment - void enroll(IBinder token, long challenge, int groupId, IFingerprintServiceReceiver receiver, + void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, int flags); // Cancel enrollment in progress diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl index e82395f..a2d74b8 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl @@ -25,7 +25,7 @@ import android.os.UserHandle; oneway interface IFingerprintServiceReceiver { void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining); void onAcquired(long deviceId, int acquiredInfo); - void onProcessed(long deviceId, int fingerId, int groupId); + void onAuthenticated(long deviceId, int fingerId, int groupId); void onError(long deviceId, int error); void onRemoved(long deviceId, int fingerId, int groupId); } diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 65d325a1..67ecb5d 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -340,6 +340,35 @@ public class Network implements Parcelable { } } + /** + * Returns a handle representing this {@code Network}, for use with the NDK API. + */ + public long getNetworkHandle() { + // The network handle is explicitly not the same as the netId. + // + // The netId is an implementation detail which might be changed in the + // future, or which alone (i.e. in the absence of some additional + // context) might not be sufficient to fully identify a Network. + // + // As such, the intention is to prevent accidental misuse of the API + // that might result if a developer assumed that handles and netIds + // were identical and passing a netId to a call expecting a handle + // "just worked". Such accidental misuse, if widely deployed, might + // prevent future changes to the semantics of the netId field or + // inhibit the expansion of state required for Network objects. + // + // This extra layer of indirection might be seen as paranoia, and might + // never end up being necessary, but the added complexity is trivial. + // At some future date it may be desirable to realign the handle with + // Multiple Provisioning Domains API recommendations, as made by the + // IETF mif working group. + // + // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // value in the native/android/net.c NDK implementation. + final long HANDLE_MAGIC = 0xfacade; + return (((long) netId) << 32) | HANDLE_MAGIC; + } + // implement the Parcelable interface public int describeContents() { return 0; diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index 00b2ee3..f10e530 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -40,6 +40,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -262,7 +263,7 @@ public final class ApduServiceInfo implements Parcelable { * for that category. * @return List of AIDs registered by the service */ - public ArrayList<String> getAids() { + public List<String> getAids() { final ArrayList<String> aids = new ArrayList<String>(); for (AidGroup group : getAidGroups()) { aids.addAll(group.aids); @@ -270,6 +271,18 @@ public final class ApduServiceInfo implements Parcelable { return aids; } + public List<String> getPrefixAids() { + final ArrayList<String> prefixAids = new ArrayList<String>(); + for (AidGroup group : getAidGroups()) { + for (String aid : group.aids) { + if (aid.endsWith("*")) { + prefixAids.add(aid); + } + } + } + return prefixAids; + } + /** * Returns the registered AID group for this category. */ diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java index e6160aa..dc96640 100644 --- a/core/java/android/os/storage/DiskInfo.java +++ b/core/java/android/os/storage/DiskInfo.java @@ -16,6 +16,7 @@ package android.os.storage; +import android.annotation.NonNull; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; @@ -59,6 +60,10 @@ public class DiskInfo implements Parcelable { volumeIds = parcel.readStringArray(); } + public @NonNull String getId() { + return id; + } + public String getDescription() { // TODO: splice vendor label into these strings if ((flags & FLAG_SD) != 0) { diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 10ffd48..0a8187e 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -923,12 +923,13 @@ public interface IMountService extends IInterface { } @Override - public VolumeInfo[] getVolumes() throws RemoteException { + public VolumeInfo[] getVolumes(int _flags) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); VolumeInfo[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(_flags); mRemote.transact(Stub.TRANSACTION_getVolumes, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArray(VolumeInfo.CREATOR); @@ -1029,6 +1030,39 @@ public interface IMountService extends IInterface { _data.recycle(); } } + + @Override + public void setVolumeNickname(String volId, String nickname) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + _data.writeString(nickname); + mRemote.transact(Stub.TRANSACTION_setVolumeNickname, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + _data.writeInt(flags); + _data.writeInt(mask); + mRemote.transact(Stub.TRANSACTION_setVolumeUserFlags, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } private static final String DESCRIPTOR = "IMountService"; @@ -1132,6 +1166,9 @@ public interface IMountService extends IInterface { static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50; static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52; + static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1566,7 +1603,8 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumes: { data.enforceInterface(DESCRIPTOR); - VolumeInfo[] volumes = getVolumes(); + int _flags = data.readInt(); + VolumeInfo[] volumes = getVolumes(_flags); reply.writeNoException(); reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; @@ -1614,6 +1652,23 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_setVolumeNickname: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + String nickname = data.readString(); + setVolumeNickname(volId, nickname); + reply.writeNoException(); + return true; + } + case TRANSACTION_setVolumeUserFlags: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + int _flags = data.readInt(); + int _mask = data.readInt(); + setVolumeUserFlags(volId, _flags, _mask); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1902,7 +1957,7 @@ public interface IMountService extends IInterface { public void waitForAsecScan() throws RemoteException; public DiskInfo[] getDisks() throws RemoteException; - public VolumeInfo[] getVolumes() throws RemoteException; + public VolumeInfo[] getVolumes(int flags) throws RemoteException; public void mount(String volId) throws RemoteException; public void unmount(String volId) throws RemoteException; @@ -1911,4 +1966,7 @@ public interface IMountService extends IInterface { public void partitionPublic(String diskId) throws RemoteException; public void partitionPrivate(String diskId) throws RemoteException; public void partitionMixed(String diskId, int ratio) throws RemoteException; + + public void setVolumeNickname(String volId, String nickname) throws RemoteException; + public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException; } diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index 3965f9d..fd914bc 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -91,6 +91,13 @@ public interface IMountServiceListener extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_onVolumeMetadataChanged: { + data.enforceInterface(DESCRIPTOR); + final VolumeInfo vol = (VolumeInfo) data.readParcelable(null); + onVolumeMetadataChanged(vol); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -175,6 +182,22 @@ public interface IMountServiceListener extends IInterface { _data.recycle(); } } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeParcelable(vol, 0); + mRemote.transact(Stub.TRANSACTION_onVolumeMetadataChanged, _data, _reply, + android.os.IBinder.FLAG_ONEWAY); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0); @@ -182,6 +205,7 @@ public interface IMountServiceListener extends IInterface { static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2); + static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3); } /** @@ -204,4 +228,6 @@ public interface IMountServiceListener extends IInterface { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) throws RemoteException; + + public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index 29d5494..28a187d 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -40,4 +40,7 @@ public class StorageEventListener { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { } + + public void onVolumeMetadataChanged(VolumeInfo vol) { + } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index eb77477..0e977ff 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -70,6 +70,9 @@ public class StorageManager { /** {@hide} */ public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical"; + /** {@hide} */ + public static final int FLAG_ALL_METADATA = 1 << 0; + private final Context mContext; private final ContentResolver mResolver; @@ -83,6 +86,7 @@ public class StorageManager { Handler.Callback { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; + private static final int MSG_VOLUME_METADATA_CHANGED = 3; final StorageEventListener mCallback; final Handler mHandler; @@ -105,6 +109,10 @@ public class StorageManager { mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); args.recycle(); return true; + case MSG_VOLUME_METADATA_CHANGED: + mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + args.recycle(); + return true; } args.recycle(); return false; @@ -132,6 +140,13 @@ public class StorageManager { args.argi3 = newState; mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + } } /** @@ -480,8 +495,13 @@ public class StorageManager { /** {@hide} */ public @NonNull List<VolumeInfo> getVolumes() { + return getVolumes(0); + } + + /** {@hide} */ + public @NonNull List<VolumeInfo> getVolumes(int flags) { try { - return Arrays.asList(mMountService.getVolumes()); + return Arrays.asList(mMountService.getVolumes(flags)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -556,6 +576,35 @@ public class StorageManager { } /** {@hide} */ + public void setVolumeNickname(String volId, String nickname) { + try { + mMountService.setVolumeNickname(volId, nickname); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeInited(String volId, boolean inited) { + try { + mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0, + VolumeInfo.USER_FLAG_INITED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeSnoozed(String volId, boolean snoozed) { + try { + mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0, + VolumeInfo.USER_FLAG_SNOOZED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ public @Nullable StorageVolume getStorageVolume(File file) { return getStorageVolume(getVolumeList(), file); } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index fe1e206..f06fc8c 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -71,6 +71,9 @@ public class VolumeInfo implements Parcelable { public static final int FLAG_PRIMARY = 1 << 0; public static final int FLAG_VISIBLE = 1 << 1; + public static final int USER_FLAG_INITED = 1 << 0; + public static final int USER_FLAG_SNOOZED = 1 << 1; + private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); @@ -104,8 +107,9 @@ public class VolumeInfo implements Parcelable { /** Framework state */ public final int mtpIndex; - public String nickname; public String diskId; + public String nickname; + public int userFlags = 0; public VolumeInfo(String id, int type, int mtpIndex) { this.id = Preconditions.checkNotNull(id); @@ -124,8 +128,9 @@ public class VolumeInfo implements Parcelable { fsLabel = parcel.readString(); path = parcel.readString(); mtpIndex = parcel.readInt(); - nickname = parcel.readString(); diskId = parcel.readString(); + nickname = parcel.readString(); + userFlags = parcel.readInt(); } public static @NonNull String getEnvironmentForState(int state) { @@ -145,6 +150,30 @@ public class VolumeInfo implements Parcelable { return getBroadcastForEnvironment(getEnvironmentForState(state)); } + public @NonNull String getId() { + return id; + } + + public @Nullable String getDiskId() { + return diskId; + } + + public int getType() { + return type; + } + + public int getState() { + return state; + } + + public @Nullable String getFsUuid() { + return fsUuid; + } + + public @Nullable String getNickname() { + return nickname; + } + public @Nullable String getDescription() { if (ID_PRIVATE_INTERNAL.equals(id)) { return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); @@ -165,6 +194,14 @@ public class VolumeInfo implements Parcelable { return (flags & FLAG_VISIBLE) != 0; } + public boolean isInited() { + return (userFlags & USER_FLAG_INITED) != 0; + } + + public boolean isSnoozed() { + return (userFlags & USER_FLAG_SNOOZED) != 0; + } + public boolean isVisibleToUser(int userId) { if (type == TYPE_PUBLIC && userId == this.userId) { return isVisible(); @@ -175,6 +212,10 @@ public class VolumeInfo implements Parcelable { } } + public File getPath() { + return new File(path); + } + public File getPathForUser(int userId) { if (path == null) { return null; @@ -284,8 +325,9 @@ public class VolumeInfo implements Parcelable { pw.println(); pw.printPair("path", path); pw.printPair("mtpIndex", mtpIndex); - pw.printPair("nickname", nickname); pw.printPair("diskId", diskId); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags)); pw.decreaseIndent(); pw.println(); } @@ -331,7 +373,8 @@ public class VolumeInfo implements Parcelable { parcel.writeString(fsLabel); parcel.writeString(path); parcel.writeInt(mtpIndex); - parcel.writeString(nickname); parcel.writeString(diskId); + parcel.writeString(nickname); + parcel.writeInt(userFlags); } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 2bcb352..7828851 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -811,7 +811,7 @@ public class StaticLayout extends Layout { float sum = 0; int i; - for (i = len; i >= 0; i--) { + for (i = len; i > 0; i--) { float w = widths[i - 1 + lineStart - widthStart]; if (w + sum + ellipsisWidth > avail) { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 8792323..c5b5c84 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -860,19 +860,22 @@ public class PopupWindow { } /** - * Set the layout type for this window. Should be one of the TYPE constants defined in - * {@link WindowManager.LayoutParams}. + * Set the layout type for this window. This value will be passed through to + * {@link WindowManager.LayoutParams#type} therefore the value should match any value + * {@link WindowManager.LayoutParams#type} accepts. * * @param layoutType Layout type for this window. - * @hide + * + * @see WindowManager.LayoutParams#type */ public void setWindowLayoutType(int layoutType) { mWindowLayoutType = layoutType; } /** - * @return The layout type for this window. - * @hide + * Returns the layout type for this window. + * + * @see #setWindowLayoutType(int) */ public int getWindowLayoutType() { return mWindowLayoutType; diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d6886bd..9ec5221 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1242,23 +1242,26 @@ <!-- Message shown during fingerprint acquisision when the fingerprint sensor needs cleaning --> <string name="fingerprint_acquired_imager_dirty">Fingerprint sensor is dirty. Please clean and try again.</string> <!-- Message shown during fingerprint acquisision when the user removes their finger from the sensor too quickly --> - <string name="fingerprint_acquired_too_fast">Finger moved to fast. Please try again.</string> + <string name="fingerprint_acquired_too_fast">Finger moved too fast. Please try again.</string> <!-- Message shown during fingerprint acquisision when the user moves their finger too slowly --> <string name="fingerprint_acquired_too_slow">Finger moved to slow. Please try again.</string> <!-- Array containing custom messages shown during fingerprint acquisision from vendor. Vendor is expected to add and translate these strings --> <string-array name="fingerprint_acquired_vendor"> </string-array> - <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint --> - <string name="fingerprint_error_unable_to_process">Unable to process. Try again.</string> <!-- Error message shown when the fingerprint hardware can't be accessed --> - <string name="fingerprint_error_hw_not_available">Hardware not available.</string> + <string name="fingerprint_error_hw_not_available">Fingerprint hardware not available.</string> <!-- Error message shown when the fingerprint hardware has run out of room for storing fingerprints --> <string name="fingerprint_error_no_space">Fingerprint can\'t be stored. Please remove an existing fingerprint.</string> <!-- Error message shown when the fingerprint hardware timer has expired and the user needs to restart the operation. --> <string name="fingerprint_error_timeout">Fingerprint time out reached. Try again.</string> - <!-- Error message shown when the fingerprint hardware timer has expired and the user needs to restart the operation. --> - <string name="fingerprint_error_vendor">Fingerprint time out reached. Try again.</string> + <!-- Generic error message shown when the fingerprint operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user--> + <string name="fingerprint_error_canceled">Fingerprint operation canceled.</string> + <!-- Generic error message shown when the fingerprint operation fails because too many attempts have been made. --> + <string name="fingerprint_error_lockout">Too many attempts. Try again later.</string> + <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint --> + <string name="fingerprint_error_unable_to_process">Try again.</string> + <!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings --> <string-array name="fingerprint_error_vendor"> </string-array> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fd75d01..e5b1cb5 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2091,6 +2091,8 @@ <java-symbol type="string" name="fingerprint_acquired_too_slow" /> <java-symbol type="string" name="fingerprint_acquired_too_fast" /> <java-symbol type="array" name="fingerprint_acquired_vendor" /> + <java-symbol type="string" name="fingerprint_error_canceled" /> + <java-symbol type="string" name="fingerprint_error_lockout" /> <!-- From various Material changes --> <java-symbol type="attr" name="titleTextAppearance" /> diff --git a/data/keyboards/AVRCP.idc b/data/keyboards/AVRCP.idc new file mode 100644 index 0000000..610b7f9 --- /dev/null +++ b/data/keyboards/AVRCP.idc @@ -0,0 +1,19 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# AVRCP +# + +device.internal = 1 diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index 0284171..8394517 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -48,11 +48,12 @@ public final class TvTrackInfo implements Parcelable { private final int mVideoWidth; private final int mVideoHeight; private final float mVideoFrameRate; + private final float mVideoPixelAspectRatio; private final Bundle mExtra; private TvTrackInfo(int type, String id, String language, String description, int audioChannelCount, int audioSampleRate, int videoWidth, int videoHeight, - float videoFrameRate, Bundle extra) { + float videoFrameRate, float videoPixelAspectRatio, Bundle extra) { mType = type; mId = id; mLanguage = language; @@ -62,6 +63,7 @@ public final class TvTrackInfo implements Parcelable { mVideoWidth = videoWidth; mVideoHeight = videoHeight; mVideoFrameRate = videoFrameRate; + mVideoPixelAspectRatio = videoPixelAspectRatio; mExtra = extra; } @@ -75,6 +77,7 @@ public final class TvTrackInfo implements Parcelable { mVideoWidth = in.readInt(); mVideoHeight = in.readInt(); mVideoFrameRate = in.readFloat(); + mVideoPixelAspectRatio = in.readFloat(); mExtra = in.readBundle(); } @@ -162,6 +165,17 @@ public final class TvTrackInfo implements Parcelable { } /** + * Returns the pixel aspect ratio (the ratio of a pixel's width to its height) of the video. + * Valid only for {@link #TYPE_VIDEO} tracks. + */ + public final float getVideoPixelAspectRatio() { + if (mType != TYPE_VIDEO) { + throw new IllegalStateException("Not a video track"); + } + return mVideoPixelAspectRatio; + } + + /** * Returns the extra information about the current track. */ public final Bundle getExtra() { @@ -190,6 +204,7 @@ public final class TvTrackInfo implements Parcelable { dest.writeInt(mVideoWidth); dest.writeInt(mVideoHeight); dest.writeFloat(mVideoFrameRate); + dest.writeFloat(mVideoPixelAspectRatio); dest.writeBundle(mExtra); } @@ -219,6 +234,7 @@ public final class TvTrackInfo implements Parcelable { private int mVideoWidth; private int mVideoHeight; private float mVideoFrameRate; + private float mVideoPixelAspectRatio = 1.0f; private Bundle mExtra; /** @@ -332,6 +348,26 @@ public final class TvTrackInfo implements Parcelable { } /** + * Sets the pixel aspect ratio (the ratio of a pixel's width to its height) of the video. + * Valid only for {@link #TYPE_VIDEO} tracks. + * <p> + * This is needed for applications to be able to scale the video properly for some video + * formats such as 720x576 4:3 and 720x576 16:9 where pixels are not square. By default, + * applications assume the value of 1.0 (square pixels), so it is not necessary to set the + * pixel aspect ratio for most video formats. + * </p> + * + * @param videoPixelAspectRatio The pixel aspect ratio of the video. + */ + public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { + if (mType != TYPE_VIDEO) { + throw new IllegalStateException("Not a video track"); + } + mVideoPixelAspectRatio = videoPixelAspectRatio; + return this; + } + + /** * Sets the extra information about the current track. * * @param extra The extra information. @@ -348,7 +384,8 @@ public final class TvTrackInfo implements Parcelable { */ public TvTrackInfo build() { return new TvTrackInfo(mType, mId, mLanguage, mDescription, mAudioChannelCount, - mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate, mExtra); + mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate, + mVideoPixelAspectRatio, mExtra); } } } diff --git a/native/android/Android.mk b/native/android/Android.mk index b3a74a8..12fdf71 100644 --- a/native/android/Android.mk +++ b/native/android/Android.mk @@ -12,9 +12,10 @@ LOCAL_SRC_FILES:= \ looper.cpp \ native_activity.cpp \ native_window.cpp \ + net.c \ obb.cpp \ sensor.cpp \ - storage_manager.cpp + storage_manager.cpp \ LOCAL_SHARED_LIBRARIES := \ liblog \ @@ -25,14 +26,17 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ libui \ libgui \ - libandroid_runtime + libandroid_runtime \ + libnetd_client \ LOCAL_STATIC_LIBRARIES := \ libstorage LOCAL_C_INCLUDES += \ frameworks/base/native/include \ - frameworks/base/core/jni/android + frameworks/base/core/jni/android \ + bionic/libc/dns/include \ + system/netd/include \ LOCAL_MODULE := libandroid diff --git a/native/android/net.c b/native/android/net.c new file mode 100644 index 0000000..de4b90c --- /dev/null +++ b/native/android/net.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <android/multinetwork.h> +#include <errno.h> +#include <NetdClient.h> // the functions that communicate with netd +#include <resolv_netid.h> // android_getaddrinfofornet() +#include <stdlib.h> +#include <sys/limits.h> + + +static int getnetidfromhandle(net_handle_t handle, unsigned *netid) { + static const uint32_t k32BitMask = 0xffffffff; + // This value MUST be kept in sync with the corresponding value in + // the android.net.Network#getNetworkHandle() implementation. + static const uint32_t kHandleMagic = 0xfacade; + + // Check for minimum acceptable version of the API in the low bits. + if (handle != NETWORK_UNSPECIFIED && + (handle & k32BitMask) != kHandleMagic) { + return 0; + } + + if (netid != NULL) { + *netid = ((handle >> (CHAR_BIT * sizeof(k32BitMask))) & k32BitMask); + } + return 1; +} + + +int android_setsocknetwork(net_handle_t network, int fd) { + unsigned netid; + if (!getnetidfromhandle(network, &netid)) { + errno = EINVAL; + return -1; + } + + int rval = setNetworkForSocket(netid, fd); + if (rval < 0) { + errno = -rval; + rval = -1; + } + return rval; +} + +int android_setprocnetwork(net_handle_t network) { + unsigned netid; + if (!getnetidfromhandle(network, &netid)) { + errno = EINVAL; + return -1; + } + + int rval = setNetworkForProcess(netid); + if (rval < 0) { + errno = -rval; + rval = -1; + } + return rval; +} + +int android_getaddrinfofornetwork(net_handle_t network, + const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + unsigned netid; + if (!getnetidfromhandle(network, &netid)) { + errno = EINVAL; + return EAI_SYSTEM; + } + + return android_getaddrinfofornet(node, service, hints, netid, 0, res); +} diff --git a/packages/DocumentsUI/res/layout/fragment_pick.xml b/packages/DocumentsUI/res/layout/fragment_pick.xml index 5735871..87dc4f8 100644 --- a/packages/DocumentsUI/res/layout/fragment_pick.xml +++ b/packages/DocumentsUI/res/layout/fragment_pick.xml @@ -21,12 +21,19 @@ android:baselineAligned="false" android:gravity="center_vertical" android:minHeight="?android:attr/listPreferredItemHeightSmall"> - + <Button + android:id="@android:id/button2" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:text="@android:string/cancel" + android:visibility="gone" + style="?android:attr/buttonBarNegativeButtonStyle" /> <Button android:id="@android:id/button1" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="match_parent" + android:layout_weight="1" android:textAllCaps="false" style="?android:attr/buttonBarPositiveButtonStyle" /> - </LinearLayout> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 3ca239a..062d433 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -65,6 +65,9 @@ <!-- Menu item that hides the sizes of displayed files [CHAR LIMIT=24] --> <string name="menu_file_size_hide">Hide file size</string> + <!-- Button label that copies files to the current directory [CHAR LIMIT=48] --> + <string name="button_copy">Copy</string> + <!-- Action mode title summarizing the number of documents selected [CHAR LIMIT=32] --> <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string> @@ -112,8 +115,6 @@ <!-- Title of dialog when prompting user to select an app to share documents with [CHAR LIMIT=32] --> <string name="share_via">Share via</string> - <!-- Title of the cancel button [CHAR LIMIT=24] --> - <string name="cancel">Cancel</string> <!-- Title of the copy notification [CHAR LIMIT=24] --> <string name="copy_notification_title">Copying files</string> <!-- Text shown on the copy notification to indicate remaining time, in minutes [CHAR LIMIT=24] --> @@ -125,5 +126,17 @@ </plurals> <!-- Text shown on the copy notification while DocumentsUI performs setup in preparation for copying files [CHAR LIMIT=32] --> <string name="copy_preparing">Preparing for copy\u2026</string> - + <!-- Title of the copy error notification [CHAR LIMIT=48] --> + <plurals name="copy_error_notification_title"> + <item quantity="one">Error copying <xliff:g id="count" example="1">%1$d</xliff:g> file.</item> + <item quantity="other">Error copying <xliff:g id="count" example="1">%1$d</xliff:g> files.</item> + </plurals> + <!-- Second line for notifications saying that more information will be shown after touching [CHAR LIMIT=48] --> + <string name="notification_touch_for_details">Touch to view details</string> + <!-- Label of a dialog button for retrying a failed operation [CHAR LIMIT=24] --> + <string name="retry">Retry</string> + <!-- Title of the copying failure alert dialog. [CHAR LIMIT=48] --> + <string name="copy_failure_alert_title">Error copying files</string> + <!-- Contents of the copying failure alert dialog. [CHAR LIMIT=48] --> + <string name="copy_failure_alert_content">Following files are not copied: <xliff:g id="list">%1$s</xliff:g></string> </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index bd17401..66792da 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -33,10 +33,6 @@ import com.android.documentsui.model.RootInfo; import com.google.common.collect.Maps; abstract class BaseActivity extends Activity { - /** Intent action name to open copy destination. */ - public static String ACTION_OPEN_COPY_DESTINATION_STRING = - "com.android.documentsui.OPEN_COPY_DESTINATION"; - public abstract State getDisplayState(); public abstract RootInfo getCurrentRoot(); public abstract void onStateChanged(); @@ -56,6 +52,18 @@ abstract class BaseActivity extends Activity { return (BaseActivity) fragment.getActivity(); } + public static abstract class DocumentsIntent { + /** Intent action name to open copy destination. */ + public static String ACTION_OPEN_COPY_DESTINATION = + "com.android.documentsui.OPEN_COPY_DESTINATION"; + + /** + * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which + * specifies if the destination directory needs to create new directory or not. + */ + public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY"; + } + public static class State implements android.os.Parcelable { public int action; public String[] acceptMimes; @@ -77,6 +85,7 @@ abstract class BaseActivity extends Activity { public boolean showAdvanced = false; public boolean stackTouched = false; public boolean restored = false; + public boolean directoryCopy = false; /** Current user navigation stack; empty implies recents. */ public DocumentStack stack = new DocumentStack(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java index e140f33..d2ef3d7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java @@ -57,6 +57,10 @@ public class CopyService extends IntentService { private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL"; public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST"; public static final String EXTRA_STACK = "com.android.documentsui.STACK"; + public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE"; + + // TODO: Move it to a shared file when more operations are implemented. + public static final int FAILURE_COPY = 1; private NotificationManager mNotificationManager; private Notification.Builder mProgressBuilder; @@ -66,7 +70,7 @@ public class CopyService extends IntentService { private volatile boolean mIsCancelled; // Parameters of the copy job. Requests to an IntentService are serialized so this code only // needs to deal with one job at a time. - private final List<Uri> mFailedFiles; + private final ArrayList<Uri> mFailedFiles; private long mBatchSize; private long mBytesCopied; private long mStartTime; @@ -128,7 +132,23 @@ public class CopyService extends IntentService { mNotificationManager.cancel(mJobId, 0); if (mFailedFiles.size() > 0) { - // TODO: Display a notification when an error has occurred. + final Context context = getApplicationContext(); + final Intent navigateIntent = new Intent(context, StandaloneActivity.class); + navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack); + navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY); + navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles); + + final Notification.Builder errorBuilder = new Notification.Builder(this) + .setContentTitle(context.getResources(). + getQuantityString(R.plurals.copy_error_notification_title, + mFailedFiles.size(), mFailedFiles.size())) + .setContentText(getString(R.string.notification_touch_for_details)) + .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT)) + .setCategory(Notification.CATEGORY_ERROR) + .setSmallIcon(R.drawable.ic_menu_copy) + .setAutoCancel(true); + mNotificationManager.notify(mJobId, 0, errorBuilder.build()); } // TODO: Display a toast if the copy was cancelled. @@ -158,18 +178,19 @@ public class CopyService extends IntentService { final Context context = getApplicationContext(); final Intent navigateIntent = new Intent(context, StandaloneActivity.class); - navigateIntent.putExtra(EXTRA_STACK, (Parcelable)stack); + navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack); mProgressBuilder = new Notification.Builder(this) .setContentTitle(getString(R.string.copy_notification_title)) .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent, 0)) .setCategory(Notification.CATEGORY_PROGRESS) - .setSmallIcon(R.drawable.ic_menu_copy).setOngoing(true); + .setSmallIcon(R.drawable.ic_menu_copy) + .setOngoing(true); final Intent cancelIntent = new Intent(this, CopyService.class); cancelIntent.putExtra(EXTRA_CANCEL, mJobId); mProgressBuilder.addAction(R.drawable.ic_cab_cancel, - getString(R.string.cancel), PendingIntent.getService(this, 0, + getString(android.R.string.cancel), PendingIntent.getService(this, 0, cancelIntent, PendingIntent.FLAG_ONE_SHOT)); // Send an initial progress notification. diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 61fcad2..e2e9807 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -683,10 +683,18 @@ public class DirectoryFragment extends Fragment { // Pop up a dialog to pick a destination. This is inadequate but works for now. // TODO: Implement a picker that is to spec. final Intent intent = new Intent( - BaseActivity.ACTION_OPEN_COPY_DESTINATION_STRING, + BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION, Uri.EMPTY, getActivity(), DocumentsActivity.class); + boolean directoryCopy = false; + for (DocumentInfo info : docs) { + if (Document.MIME_TYPE_DIR.equals(info.mimeType)) { + directoryCopy = true; + break; + } + } + intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy); startActivityForResult(intent, REQUEST_COPY_DESTINATION); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 1e798eb..a2a789f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -237,7 +237,7 @@ public class DocumentsActivity extends BaseActivity { mState.action = ACTION_MANAGE; } else if (DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT.equals(action)) { mState.action = ACTION_BROWSE; - } else if (ACTION_OPEN_COPY_DESTINATION_STRING.equals(action)) { + } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) { mState.action = ACTION_OPEN_COPY_DESTINATION; } @@ -265,6 +265,10 @@ public class DocumentsActivity extends BaseActivity { } else { mState.showSize = LocalPreferences.getDisplayFileSize(this); } + if (mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.directoryCopy = intent.getBooleanExtra( + BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false); + } } private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> { @@ -906,7 +910,7 @@ public class DocumentsActivity extends BaseActivity { if (pick != null) { final CharSequence displayName = (mState.stack.size() <= 1) ? root.title : cwd.displayName; - pick.setPickTarget(cwd, displayName); + pick.setPickTarget(mState.action, cwd, displayName); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java new file mode 100644 index 0000000..1748c9c --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.text.Html; + +import com.android.documentsui.model.DocumentInfo; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +/** + * Alert dialog for failed operations. + */ +public class FailureDialogFragment extends DialogFragment + implements DialogInterface.OnClickListener { + private static final String TAG = "FailureDialogFragment"; + + private int mFailure; + private ArrayList<Uri> mFailedSrcList; + + public static void show(FragmentManager fm, int failure, ArrayList<Uri> failedSrcList) { + // TODO: Add support for other failures than copy. + if (failure != CopyService.FAILURE_COPY) { + return; + } + + final Bundle args = new Bundle(); + args.putInt(CopyService.EXTRA_FAILURE, failure); + args.putParcelableArrayList(CopyService.EXTRA_SRC_LIST, failedSrcList); + + final FragmentTransaction ft = fm.beginTransaction(); + final FailureDialogFragment fragment = new FailureDialogFragment(); + fragment.setArguments(args); + + ft.add(fragment, TAG); + ft.commitAllowingStateLoss(); + } + + @Override + public void onClick(DialogInterface dialog, int whichButton) { + // TODO: Pass mFailure and mFailedSrcList to the parent fragment. + } + + @Override + public Dialog onCreateDialog(Bundle inState) { + super.onCreate(inState); + + mFailure = getArguments().getInt(CopyService.EXTRA_FAILURE); + mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST); + + final StringBuilder list = new StringBuilder("<p>"); + for (Uri documentUri : mFailedSrcList) { + try { + final DocumentInfo documentInfo = DocumentInfo.fromUri( + getActivity().getContentResolver(), documentUri); + list.append(String.format("• %s<br>", documentInfo.displayName)); + } + catch (FileNotFoundException ignore) { + // Source file most probably gone. + } + } + list.append("</p>"); + final String message = String.format(getString(R.string.copy_failure_alert_content), + list.toString()); + + return new AlertDialog.Builder(getActivity()) + .setTitle(getString(R.string.copy_failure_alert_title)) + .setMessage(Html.fromHtml(message)) + // TODO: Implement retrying the copy operation. + .setPositiveButton(R.string.retry, this) + .setNegativeButton(android.R.string.cancel, this) + .setIcon(android.R.drawable.ic_dialog_alert) + .create(); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java index 4b008ca..5e565bf 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import android.R.string; +import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -40,6 +42,7 @@ public class PickFragment extends Fragment { private View mContainer; private Button mPick; + private Button mCancel; public static void show(FragmentManager fm) { final PickFragment fragment = new PickFragment(); @@ -61,7 +64,10 @@ public class PickFragment extends Fragment { mPick = (Button) mContainer.findViewById(android.R.id.button1); mPick.setOnClickListener(mPickListener); - setPickTarget(null, null); + mCancel = (Button) mContainer.findViewById(android.R.id.button2); + mCancel.setOnClickListener(mCancelListener); + + setPickTarget(0, null, null); return mContainer; } @@ -74,18 +80,43 @@ public class PickFragment extends Fragment { } }; - public void setPickTarget(DocumentInfo pickTarget, CharSequence displayName) { - mPickTarget = pickTarget; + private View.OnClickListener mCancelListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + final BaseActivity activity = BaseActivity.get(PickFragment.this); + activity.setResult(Activity.RESULT_CANCELED); + activity.finish(); + } + }; + /** + * @param action Which action defined in BaseActivity.State is the picker shown for. + */ + public void setPickTarget(int action, + DocumentInfo pickTarget, + CharSequence displayName) { if (mContainer != null) { - if (mPickTarget != null) { + if (pickTarget != null) { mContainer.setVisibility(View.VISIBLE); final Locale locale = getResources().getConfiguration().locale; - final String raw = getString(R.string.menu_select).toUpperCase(locale); - mPick.setText(TextUtils.expandTemplate(raw, displayName)); + switch (action) { + case BaseActivity.State.ACTION_OPEN_TREE: + final String raw = getString(R.string.menu_select).toUpperCase(locale); + mPick.setText(TextUtils.expandTemplate(raw, displayName)); + mCancel.setVisibility(View.GONE); + break; + case BaseActivity.State.ACTION_OPEN_COPY_DESTINATION: + mPick.setText(getString(R.string.button_copy).toUpperCase(locale)); + mCancel.setVisibility(View.VISIBLE); + break; + default: + throw new IllegalArgumentException("Illegal action for PickFragment."); + } + } else { mContainer.setVisibility(View.GONE); } } + mPickTarget = pickTarget; } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index d2267b1..27e8f20 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -367,6 +367,9 @@ public class RootsCache { if (!state.showAdvanced && advanced) continue; // Exclude non-local devices when local only if (state.localOnly && !localOnly) continue; + // Exclude downloads roots that don't support directory creation + // TODO: Add flag to check the root supports directory creation or not. + if (state.directoryCopy && root.isDownloads()) continue; // Only show empty roots when creating if ((state.action != State.ACTION_CREATE || state.action != State.ACTION_OPEN_TREE || diff --git a/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java b/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java index f542838..976f21d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java @@ -63,6 +63,7 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.Toolbar; +import com.android.documentsui.FailureDialogFragment; import com.android.documentsui.RecentsProvider.ResumeColumns; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; @@ -73,6 +74,7 @@ import libcore.io.IoUtils; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -153,6 +155,13 @@ public class StandaloneActivity extends BaseActivity { RootsFragment.show(getFragmentManager(), null); if (!mState.restored) { new RestoreStackTask().execute(); + final Intent intent = getIntent(); + final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0); + if (failure != 0) { + final ArrayList<Uri> failedSrcList = intent.getParcelableArrayListExtra( + CopyService.EXTRA_SRC_LIST); + FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList); + } } else { onCurrentDirectoryChanged(ANIM_NONE); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 410a7e4..094cd1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -28,6 +28,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; +import android.hardware.fingerprint.FingerprintManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; @@ -625,6 +626,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void onFingerprintError(int msgId, String errString) { // TODO: Go to bouncer if this is "too many attempts" (lockout) error. Log.i(TAG, "FP Error: " + errString); + updateLockIcon(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 6830957..818f5ee 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -20,7 +20,10 @@ import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.UserHandle; import android.os.storage.DiskInfo; import android.os.storage.StorageEventListener; @@ -38,6 +41,8 @@ public class StorageNotification extends SystemUI { private static final int NOTIF_ID = 0x53544f52; // STOR + private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; + // TODO: delay some notifications to avoid bumpy fast operations // TODO: annoy user when private media is missing @@ -49,6 +54,25 @@ public class StorageNotification extends SystemUI { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { onVolumeStateChangedInternal(vol, oldState, newState); } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) { + // Avoid kicking notifications when getting early metadata before + // mounted. If already mounted, we're being kicked because of a + // nickname or init'ed change. + if (vol.getState() == VolumeInfo.STATE_MOUNTED) { + onVolumeStateChangedInternal(vol, vol.getState(), vol.getState()); + } + } + }; + + private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // TODO: kick this onto background thread + final String volId = intent.getStringExtra(VolumeInfo.EXTRA_VOLUME_ID); + mStorageManager.setVolumeSnoozed(volId, true); + } }; @Override @@ -58,23 +82,26 @@ public class StorageNotification extends SystemUI { mStorageManager = mContext.getSystemService(StorageManager.class); mStorageManager.registerListener(mListener); + mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME), + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + // Kick current state into place final List<VolumeInfo> vols = mStorageManager.getVolumes(); for (VolumeInfo vol : vols) { - onVolumeStateChangedInternal(vol, vol.state, vol.state); + onVolumeStateChangedInternal(vol, vol.getState(), vol.getState()); } } public void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) { // We only care about public volumes - if (vol.type != VolumeInfo.TYPE_PUBLIC) { + if (vol.getType() != VolumeInfo.TYPE_PUBLIC) { return; } Log.d(TAG, vol.toString()); // New state means we tear down any old notifications - mNotificationManager.cancelAsUser(vol.id, NOTIF_ID, UserHandle.ALL); + mNotificationManager.cancelAsUser(vol.getId(), NOTIF_ID, UserHandle.ALL); switch (newState) { case VolumeInfo.STATE_UNMOUNTED: @@ -106,7 +133,7 @@ public class StorageNotification extends SystemUI { } private void onVolumeMounting(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_checking_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -119,13 +146,16 @@ public class StorageNotification extends SystemUI { .setOngoing(true) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeMounted(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + // Don't annoy when user dismissed in past + if (vol.isSnoozed()) return; + + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final Notification notif; - if (disk.isAdoptable()) { + if (disk.isAdoptable() && !vol.isInited()) { final CharSequence title = disk.getDescription(); final CharSequence text = mContext.getString( R.string.ext_media_new_notification_message, disk.getDescription()); @@ -136,6 +166,7 @@ public class StorageNotification extends SystemUI { buildInitPendingIntent(vol))) .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), buildUnmountPendingIntent(vol))) + .setDeleteIntent(buildSnoozeIntent(vol)) .setCategory(Notification.CATEGORY_SYSTEM) .build(); @@ -150,12 +181,13 @@ public class StorageNotification extends SystemUI { buildBrowsePendingIntent(vol))) .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), buildUnmountPendingIntent(vol))) + .setDeleteIntent(buildSnoozeIntent(vol)) .setCategory(Notification.CATEGORY_SYSTEM) .setPriority(Notification.PRIORITY_LOW) .build(); } - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeFormatting(VolumeInfo vol) { @@ -163,7 +195,7 @@ public class StorageNotification extends SystemUI { } private void onVolumeUnmounting(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_unmounting_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -176,11 +208,11 @@ public class StorageNotification extends SystemUI { .setOngoing(true) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeUnmountable(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_unmountable_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -192,7 +224,7 @@ public class StorageNotification extends SystemUI { .setCategory(Notification.CATEGORY_ERROR) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeRemoved(VolumeInfo vol) { @@ -201,7 +233,7 @@ public class StorageNotification extends SystemUI { return; } - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_nomedia_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -212,7 +244,7 @@ public class StorageNotification extends SystemUI { .setCategory(Notification.CATEGORY_ERROR) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private Notification.Builder buildNotificationBuilder(CharSequence title, CharSequence text) { @@ -229,28 +261,49 @@ public class StorageNotification extends SystemUI { final Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.deviceinfo.StorageWizardInit"); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id); - return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) { final Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.deviceinfo.StorageUnmountReceiver"); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id); - return PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, UserHandle.CURRENT); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); } private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) { final Intent intent = vol.buildBrowseIntent(); - return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } private PendingIntent buildDetailsPendingIntent(VolumeInfo vol) { final Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$StorageVolumeSettingsActivity"); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id); - return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildSnoozeIntent(VolumeInfo vol) { + final Intent intent = new Intent(ACTION_SNOOZE_VOLUME); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); } } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index a99f387..4c937f7 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -16,6 +16,13 @@ package com.android.server; +import static com.android.internal.util.XmlUtils.readIntAttribute; +import static com.android.internal.util.XmlUtils.readStringAttribute; +import static com.android.internal.util.XmlUtils.writeIntAttribute; +import static com.android.internal.util.XmlUtils.writeStringAttribute; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + import android.Manifest; import android.app.ActivityManagerNative; import android.app.AppOpsManager; @@ -55,9 +62,13 @@ import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.DebugUtils; import android.util.Log; import android.util.Slog; +import android.util.Xml; +import libcore.io.IoUtils; import libcore.util.EmptyArray; import libcore.util.HexEncoding; @@ -66,14 +77,21 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IMediaContainerService; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.NativeDaemonConnector.Command; import com.android.server.NativeDaemonConnector.SensitiveArg; import com.android.server.pm.PackageManagerService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -214,6 +232,57 @@ class MountService extends IMountService.Stub public static final int FstrimCompleted = 700; } + private static final String TAG_VOLUMES = "volumes"; + private static final String TAG_VOLUME = "volume"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_FS_UUID = "fsUuid"; + private static final String ATTR_NICKNAME = "nickname"; + private static final String ATTR_USER_FLAGS = "userFlags"; + + private final AtomicFile mMetadataFile; + + private static class VolumeMetadata { + public final int type; + public final String fsUuid; + public String nickname; + public int userFlags; + + public VolumeMetadata(int type, String fsUuid) { + this.type = type; + this.fsUuid = Preconditions.checkNotNull(fsUuid); + } + + public static VolumeMetadata read(XmlPullParser in) throws IOException { + final int type = readIntAttribute(in, ATTR_TYPE); + final String fsUuid = readStringAttribute(in, ATTR_FS_UUID); + final VolumeMetadata meta = new VolumeMetadata(type, fsUuid); + meta.nickname = readStringAttribute(in, ATTR_NICKNAME); + meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS); + return meta; + } + + public static void write(XmlSerializer out, VolumeMetadata meta) throws IOException { + out.startTag(null, TAG_VOLUME); + writeIntAttribute(out, ATTR_TYPE, meta.type); + writeStringAttribute(out, ATTR_FS_UUID, meta.fsUuid); + writeStringAttribute(out, ATTR_NICKNAME, meta.nickname); + writeIntAttribute(out, ATTR_USER_FLAGS, meta.userFlags); + out.endTag(null, TAG_VOLUME); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("VolumeMetadata:"); + pw.increaseIndent(); + pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type)); + pw.printPair("fsUuid", fsUuid); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", + DebugUtils.flagsToString(VolumeInfo.class, "USER_FLAG_", userFlags)); + pw.decreaseIndent(); + pw.println(); + } + } + /** * <em>Never</em> hold the lock while performing downcalls into vold, since * unsolicited events can suddenly appear to update data structures. @@ -222,11 +291,18 @@ class MountService extends IMountService.Stub @GuardedBy("mLock") private int[] mStartedUsers = EmptyArray.INT; + + /** Map from disk ID to disk */ @GuardedBy("mLock") private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>(); + /** Map from volume ID to disk */ @GuardedBy("mLock") private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>(); + /** Map from UUID to metadata */ + @GuardedBy("mLock") + private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>(); + private DiskInfo findDiskById(String id) { synchronized (mLock) { final DiskInfo disk = mDisks.get(id); @@ -260,6 +336,15 @@ class MountService extends IMountService.Stub throw new IllegalArgumentException("No volume found for path " + path); } + private VolumeMetadata findOrCreateMetadataLocked(VolumeInfo vol) { + VolumeMetadata meta = mMetadata.get(vol.fsUuid); + if (meta == null) { + meta = new VolumeMetadata(vol.type, vol.fsUuid); + mMetadata.put(meta.fsUuid, meta); + } + return meta; + } + private static int sNextMtpIndex = 1; private static int allocateMtpIndex(String volId) { @@ -799,6 +884,7 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsType = cooked[2]; } + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_FS_UUID_CHANGED: { @@ -807,6 +893,8 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsUuid = cooked[2]; } + refreshMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: { @@ -815,6 +903,7 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsLabel = cooked[2]; } + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_PATH_CHANGED: { @@ -901,6 +990,25 @@ class MountService extends IMountService.Stub } } + /** + * Refresh latest metadata into any currently active {@link VolumeInfo}. + */ + private void refreshMetadataLocked() { + final int size = mVolumes.size(); + for (int i = 0; i < size; i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + final VolumeMetadata meta = mMetadata.get(vol.fsUuid); + + if (meta != null) { + vol.nickname = meta.nickname; + vol.userFlags = meta.userFlags; + } else { + vol.nickname = null; + vol.userFlags = 0; + } + } + } + private void enforcePermission(String perm) { mContext.enforceCallingOrSelfPermission(perm, perm); } @@ -949,6 +1057,13 @@ class MountService extends IMountService.Stub mLastMaintenance = mLastMaintenanceFile.lastModified(); } + mMetadataFile = new AtomicFile( + new File(Environment.getSystemSecureDirectory(), "storage.xml")); + + synchronized (mLock) { + readMetadataLocked(); + } + /* * Create the connection to vold with a maximum queue of twice the * amount of containers we'd ever expect to have. This keeps an @@ -972,6 +1087,61 @@ class MountService extends IMountService.Stub mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); } + private void readMetadataLocked() { + mMetadata.clear(); + + FileInputStream fis = null; + try { + fis = mMetadataFile.openRead(); + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, null); + + int type; + while ((type = in.next()) != END_DOCUMENT) { + if (type == START_TAG) { + final String tag = in.getName(); + if (TAG_VOLUME.equals(tag)) { + final VolumeMetadata meta = VolumeMetadata.read(in); + mMetadata.put(meta.fsUuid, meta); + } + } + } + } catch (FileNotFoundException e) { + // Missing metadata is okay, probably first boot + } catch (IOException e) { + Slog.wtf(TAG, "Failed reading metadata", e); + } catch (XmlPullParserException e) { + Slog.wtf(TAG, "Failed reading metadata", e); + } finally { + IoUtils.closeQuietly(fis); + } + } + + private void writeMetadataLocked() { + FileOutputStream fos = null; + try { + fos = mMetadataFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.startTag(null, TAG_VOLUMES); + final int size = mMetadata.size(); + for (int i = 0; i < size; i++) { + final VolumeMetadata meta = mMetadata.valueAt(i); + VolumeMetadata.write(out, meta); + } + out.endTag(null, TAG_VOLUMES); + out.endDocument(); + + mMetadataFile.finishWrite(fos); + } catch (IOException e) { + if (fos != null) { + mMetadataFile.failWrite(fos); + } + } + } + /** * Exposed API calls below here */ @@ -1126,6 +1296,36 @@ class MountService extends IMountService.Stub } @Override + public void setVolumeNickname(String volId, String nickname) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + waitForReady(); + + synchronized (mLock) { + final VolumeInfo vol = findVolumeById(volId); + final VolumeMetadata meta = findOrCreateMetadataLocked(vol); + meta.nickname = nickname; + refreshMetadataLocked(); + writeMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + } + } + + @Override + public void setVolumeUserFlags(String volId, int flags, int mask) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + waitForReady(); + + synchronized (mLock) { + final VolumeInfo vol = findVolumeById(volId); + final VolumeMetadata meta = findOrCreateMetadataLocked(vol); + meta.userFlags = (meta.userFlags & ~mask) | (flags & mask); + refreshMetadataLocked(); + writeMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + } + } + + @Override public int[] getStorageUsers(String path) { enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); @@ -1909,7 +2109,12 @@ class MountService extends IMountService.Stub } @Override - public VolumeInfo[] getVolumes() { + public VolumeInfo[] getVolumes(int flags) { + if ((flags & StorageManager.FLAG_ALL_METADATA) != 0) { + // TODO: implement support for returning all metadata + throw new UnsupportedOperationException(); + } + synchronized (mLock) { final VolumeInfo[] res = new VolumeInfo[mVolumes.size()]; for (int i = 0; i < mVolumes.size(); i++) { @@ -2422,6 +2627,7 @@ class MountService extends IMountService.Stub private static class Callbacks extends Handler { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; + private static final int MSG_VOLUME_METADATA_CHANGED = 3; private final RemoteCallbackList<IMountServiceListener> mCallbacks = new RemoteCallbackList<>(); @@ -2465,6 +2671,10 @@ class MountService extends IMountService.Stub callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); break; } + case MSG_VOLUME_METADATA_CHANGED: { + callback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + break; + } } } @@ -2483,6 +2693,12 @@ class MountService extends IMountService.Stub args.argi3 = newState; obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } + + private void notifyVolumeMetadataChanged(VolumeInfo vol) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + } } @Override @@ -2539,6 +2755,15 @@ class MountService extends IMountService.Stub vol.dump(pw); } pw.decreaseIndent(); + + pw.println(); + pw.println("Metadata:"); + pw.increaseIndent(); + for (int i = 0; i < mMetadata.size(); i++) { + final VolumeMetadata meta = mMetadata.valueAt(i); + meta.dump(pw); + } + pw.decreaseIndent(); } pw.println(); diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index c150b60..90e69d7 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -39,6 +39,7 @@ import static android.Manifest.permission.USE_FINGERPRINT; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -51,9 +52,9 @@ import java.util.List; public class FingerprintService extends SystemService { private static final String TAG = "FingerprintService"; private static final boolean DEBUG = true; - private ClientData mAuthClient = null; - private ClientData mEnrollClient = null; - private ClientData mRemoveClient = null; + private ClientMonitor mAuthClient = null; + private ClientMonitor mEnrollClient = null; + private ClientMonitor mRemoveClient = null; private static final int MSG_NOTIFY = 10; @@ -63,7 +64,6 @@ public class FingerprintService extends SystemService { // Must agree with the list in fingerprint.h private static final int FINGERPRINT_ERROR = -1; private static final int FINGERPRINT_ACQUIRED = 1; - private static final int FINGERPRINT_PROCESSED = 2; private static final int FINGERPRINT_TEMPLATE_ENROLLING = 3; private static final int FINGERPRINT_TEMPLATE_REMOVED = 4; private static final int FINGERPRINT_AUTHENTICATED = 5; @@ -83,80 +83,21 @@ public class FingerprintService extends SystemService { }; private Context mContext; private int mHalDeviceId; + private int mFailedAttempts; + private final Runnable mLockoutReset = new Runnable() { + @Override + public void run() { + resetFailedAttempts(); + } + }; private static final int STATE_IDLE = 0; private static final int STATE_AUTHENTICATING = 1; private static final int STATE_ENROLLING = 2; private static final int STATE_REMOVING = 3; private static final long MS_PER_SEC = 1000; - - private class ClientData { - IBinder token; - IFingerprintServiceReceiver receiver; - int userId; - long opId; - private TokenWatcher tokenWatcher; - public ClientData(IBinder token, long opId, IFingerprintServiceReceiver receiver, - int userId) { - this.token = token; - this.opId = opId; - this.receiver = receiver; - this.userId = userId; - tokenWatcher = new TokenWatcher(token); - try { - token.linkToDeath(tokenWatcher, 0); - } catch (RemoteException e) { - Slog.w(TAG, "caught remote exception in linkToDeath: ", e); - } - } - - IBinder getToken() { - return tokenWatcher.getToken(); - } - - public void destroy() { - token.unlinkToDeath(tokenWatcher, 0); - tokenWatcher.token = null; - } - } - - private class TokenWatcher implements IBinder.DeathRecipient { - WeakReference<IBinder> token; - - TokenWatcher(IBinder token) { - this.token = new WeakReference<IBinder>(token); - } - - IBinder getToken() { - return token.get(); - } - - public void binderDied() { - if (mAuthClient != null & mAuthClient.token == token) - mAuthClient = null; - if (mEnrollClient != null && mEnrollClient.token == token) - mEnrollClient = null; - this.token = null; - } - - protected void finalize() throws Throwable { - try { - if (token != null) { - if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token); - if (mAuthClient != null && mAuthClient.token == token) { - mAuthClient.destroy(); - mAuthClient = null; - } - if (mEnrollClient != null && mEnrollClient.token == token) { - mAuthClient.destroy(); - mEnrollClient = null; - } - } - } finally { - super.finalize(); - } - } - } + private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; + private static final int MAX_FAILED_ATTEMPTS = 5; public FingerprintService(Context context) { super(context); @@ -166,11 +107,11 @@ public class FingerprintService extends SystemService { // TODO: Move these into separate process // JNI methods to communicate from FingerprintService to HAL - static native int nativeEnroll(long challenge, int groupId, int timeout); + static native int nativeEnroll(byte [] token, int groupId, int timeout); static native long nativePreEnroll(); static native int nativeStopEnrollment(); static native int nativeAuthenticate(long sessionId, int groupId); - static native int nativeStopAuthentication(long sessionId); + static native int nativeStopAuthentication(); static native int nativeRemove(int fingerId, int groupId); static native int nativeOpenHal(); static native int nativeCloseHal(); @@ -201,82 +142,62 @@ public class FingerprintService extends SystemService { Slog.v(TAG, "handleNotify(type=" + type + ", arg1=" + arg1 + ", arg2=" + arg2 + ")" + ", mAuthClients = " + mAuthClient + ", mEnrollClient = " + mEnrollClient); if (mEnrollClient != null) { - try { - final IBinder token = mEnrollClient.token; - if (doNotify(mEnrollClient, type, arg1, arg2, arg3)) { - stopEnrollment(token); - } - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to mEnrollClient. Did it die?", e); - mEnrollClient.destroy(); - mEnrollClient = null; + final IBinder token = mEnrollClient.token; + if (doNotify(mEnrollClient, type, arg1, arg2, arg3)) { + stopEnrollment(token); } } if (mAuthClient != null) { - try { - final IBinder token = mAuthClient.getToken(); - if (doNotify(mAuthClient, type, arg1, arg2, arg3)) { - stopAuthentication(token); - } - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to mAuthClient. Did it die?", e); - mAuthClient.destroy(); - mAuthClient = null; + final IBinder token = mAuthClient.token; + if (doNotify(mAuthClient, type, arg1, arg2, arg3)) { + stopAuthentication(token); } } if (mRemoveClient != null) { - try { - if (doNotify(mRemoveClient, type, arg1, arg2, arg3)) { - mRemoveClient.destroy(); - mRemoveClient = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to mRemoveClient. Did it die?", e); - mRemoveClient.destroy(); - mRemoveClient = null; + if (doNotify(mRemoveClient, type, arg1, arg2, arg3)) { + removeClient(mRemoveClient); } } } // Returns true if the operation is done, i.e. authentication completed - boolean doNotify(ClientData clientData, int type, int arg1, int arg2, int arg3) - throws RemoteException { - if (clientData.receiver == null) { - if (DEBUG) Slog.v(TAG, "receiver not registered!!"); - return false; - } + boolean doNotify(ClientMonitor clientMonitor, int type, int arg1, int arg2, int arg3) { ContentResolver contentResolver = mContext.getContentResolver(); boolean operationCompleted = false; switch (type) { case FINGERPRINT_ERROR: - clientData.receiver.onError(mHalDeviceId, arg1 /* error */); - if (arg1 == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { - if (mEnrollClient != null) { - mEnrollClient.destroy(); - mEnrollClient = null; - } - if (mAuthClient != null) { - mAuthClient.destroy(); - mAuthClient = null; - } + { + final int error = arg1; + clientMonitor.sendError(error); + removeClient(clientMonitor); + operationCompleted = true; // any error means the operation is done } - operationCompleted = true; // any error means the operation is done break; case FINGERPRINT_ACQUIRED: - clientData.receiver.onAcquired(mHalDeviceId, arg1 /* acquireInfo */); + clientMonitor.sendAcquired(arg1 /* acquireInfo */); break; - case FINGERPRINT_PROCESSED: - clientData.receiver.onProcessed(mHalDeviceId, arg1 /* fpId */, arg2 /* gpId */); - operationCompleted = true; // we either got a positive or negative match + case FINGERPRINT_AUTHENTICATED: + { + final int fpId = arg1; + final int groupId = arg2; + clientMonitor.sendAuthenticated(fpId, groupId); + if (fpId == 0) { + if (clientMonitor == mAuthClient) { + operationCompleted = handleFailedAttempt(clientMonitor); + } + } else { + mLockoutReset.run(); // a valid fingerprint resets lockout + } + } break; case FINGERPRINT_TEMPLATE_ENROLLING: { final int fpId = arg1; final int groupId = arg2; final int remaining = arg3; - clientData.receiver.onEnrollResult(mHalDeviceId, fpId, groupId, remaining); + clientMonitor.sendEnrollResult(fpId, groupId, remaining); if (remaining == 0) { - addTemplateForUser(clientData, contentResolver, fpId); + addTemplateForUser(clientMonitor, contentResolver, fpId); operationCompleted = true; // enroll completed } } @@ -285,11 +206,11 @@ public class FingerprintService extends SystemService { { final int fingerId = arg1; final int groupId = arg2; - removeTemplateForUser(clientData, contentResolver, fingerId); + removeTemplateForUser(clientMonitor, contentResolver, fingerId); if (fingerId == 0) { operationCompleted = true; // remove completed } else { - clientData.receiver.onRemoved(mHalDeviceId, fingerId, groupId); + clientMonitor.sendRemoved(fingerId, groupId); } } break; @@ -297,24 +218,60 @@ public class FingerprintService extends SystemService { return operationCompleted; } - private void removeTemplateForUser(ClientData clientData, ContentResolver contentResolver, + private void removeClient(ClientMonitor clientMonitor) { + if (clientMonitor == null) return; + clientMonitor.destroy(); + if (clientMonitor == mAuthClient) { + mAuthClient = null; + } else if (clientMonitor == mEnrollClient) { + mEnrollClient = null; + } else if (clientMonitor == mRemoveClient) { + mRemoveClient = null; + } + } + + private boolean inLockoutMode() { + return mFailedAttempts > MAX_FAILED_ATTEMPTS; + } + + private void resetFailedAttempts() { + if (DEBUG) Slog.v(TAG, "Reset fingerprint lockout"); + mFailedAttempts = 0; + } + + private boolean handleFailedAttempt(ClientMonitor clientMonitor) { + mFailedAttempts++; + if (mFailedAttempts > MAX_FAILED_ATTEMPTS) { + // Failing multiple times will continue to push out the lockout time. + mHandler.removeCallbacks(mLockoutReset); + mHandler.postDelayed(mLockoutReset, FAIL_LOCKOUT_TIMEOUT_MS); + if (clientMonitor != null + && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { + Slog.w(TAG, "Cannot send lockout message to client"); + } + return true; + } + return false; + } + + private void removeTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver, final int fingerId) { FingerprintUtils.removeFingerprintIdForUser(fingerId, contentResolver, - clientData.userId); + clientMonitor.userId); } - private void addTemplateForUser(ClientData clientData, ContentResolver contentResolver, + private void addTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver, final int fingerId) { FingerprintUtils.addFingerprintIdForUser(contentResolver, fingerId, - clientData.userId); + clientMonitor.userId); } - void startEnrollment(IBinder token, long opId, - int groupId, IFingerprintServiceReceiver receiver, int flags) { + void startEnrollment(IBinder token, byte[] cryptoToken, int groupId, + IFingerprintServiceReceiver receiver, int flags) { stopPendingOperations(); - mEnrollClient = new ClientData(token, opId, receiver, groupId); + mEnrollClient = new ClientMonitor(token, receiver, groupId); final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); - final int result = nativeEnroll(opId, groupId, timeout); + final int result = nativeEnroll(cryptoToken, groupId, timeout); if (result != 0) { Slog.w(TAG, "startEnroll failed, result=" + result); } @@ -335,8 +292,11 @@ public class FingerprintService extends SystemService { } void stopEnrollment(IBinder token) { - if (mEnrollClient == null || mEnrollClient.token != token) return; + final ClientMonitor client = mEnrollClient; + if (client == null || client.token != token) return; int result = nativeStopEnrollment(); + client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + removeClient(mEnrollClient); if (result != 0) { Slog.w(TAG, "startEnrollCancel failed, result=" + result); } @@ -345,7 +305,15 @@ public class FingerprintService extends SystemService { void startAuthentication(IBinder token, long opId, int groupId, IFingerprintServiceReceiver receiver, int flags) { stopPendingOperations(); - mAuthClient = new ClientData(token, opId, receiver, groupId); + mAuthClient = new ClientMonitor(token, receiver, groupId); + if (inLockoutMode()) { + Slog.v(TAG, "In lockout mode; disallowing authentication"); + if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { + Slog.w(TAG, "Cannot send timeout message to client"); + } + mAuthClient = null; + return; + } final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); final int result = nativeAuthenticate(opId, groupId); if (result != 0) { @@ -354,8 +322,11 @@ public class FingerprintService extends SystemService { } void stopAuthentication(IBinder token) { - if (mAuthClient == null || mAuthClient.token != token) return; - int result = nativeStopAuthentication(mAuthClient.opId); + final ClientMonitor client = mAuthClient; + if (client == null || client.token != token) return; + int result = nativeStopAuthentication(); + client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + removeClient(mAuthClient); if (result != 0) { Slog.w(TAG, "stopAuthentication failed, result=" + result); } @@ -363,7 +334,7 @@ public class FingerprintService extends SystemService { void startRemove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver) { - mRemoveClient = new ClientData(token, 0, receiver, userId); + mRemoveClient = new ClientMonitor(token, receiver, userId); // The fingerprint template ids will be removed when we get confirmation from the HAL final int result = nativeRemove(fingerId, userId); if (result != 0) { @@ -392,17 +363,114 @@ public class FingerprintService extends SystemService { "Must have " + permission + " permission."); } - private static final class Message { + private class ClientMonitor implements IBinder.DeathRecipient { IBinder token; - long opId; - int groupId; - int flags; + WeakReference<IFingerprintServiceReceiver> receiver; + int userId; - public Message(IBinder token, long challenge, int groupId, int flags) { + public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId) { this.token = token; - this.opId = challenge; - this.groupId = groupId; - this.flags = flags; + this.receiver = new WeakReference<IFingerprintServiceReceiver>(receiver); + this.userId = userId; + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.w(TAG, "caught remote exception in linkToDeath: ", e); + } + } + + public void destroy() { + if (token != null) { + token.unlinkToDeath(this, 0); + token = null; + } + receiver = null; + } + + public void binderDied() { + token = null; + removeClient(this); + } + + protected void finalize() throws Throwable { + try { + if (token != null) { + if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token); + removeClient(this); + } + } finally { + super.finalize(); + } + } + + private boolean sendRemoved(int fingerId, int groupId) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx != null) { + try { + rx.onRemoved(mHalDeviceId, fingerId, groupId); + return true; + } catch (RemoteException e) { + if (DEBUG) Slog.v(TAG, "Failed to invoke sendRemoved:", e); + } + } + removeClient(this); + return false; + } + + private boolean sendEnrollResult(int fpId, int groupId, int remaining) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx != null) { + try { + rx.onEnrollResult(mHalDeviceId, fpId, groupId, remaining); + return true; + } catch (RemoteException e) { + if (DEBUG) Slog.v(TAG, "Failed to invoke sendEnrollResult:", e); + } + } + removeClient(this); + return false; + } + + private boolean sendAuthenticated(int fpId, int groupId) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx != null) { + try { + rx.onAuthenticated(mHalDeviceId, fpId, groupId); + return true; + } catch (RemoteException e) { + if (DEBUG) Slog.v(TAG, "Failed to invoke sendProcessed:", e); + } + } + removeClient(this); + return false; + } + + private boolean sendAcquired(int acquiredInfo) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx != null) { + try { + rx.onAcquired(mHalDeviceId, acquiredInfo); + return true; + } catch (RemoteException e) { + if (DEBUG) Slog.v(TAG, "Failed to invoke sendAcquired:", e); + } + } + removeClient(this); + return false; + } + + private boolean sendError(int error) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx != null) { + try { + rx.onError(mHalDeviceId, error); + return true; + } catch (RemoteException e) { + if (DEBUG) Slog.v(TAG, "Failed to invoke sendError:", e); + } + } + removeClient(this); + return false; } } @@ -415,13 +483,14 @@ public class FingerprintService extends SystemService { @Override // Binder call - public void enroll(final IBinder token, final long opid, final int groupId, + public void enroll(final IBinder token, final byte[] cryptoToken, final int groupId, final IFingerprintServiceReceiver receiver, final int flags) { checkPermission(MANAGE_FINGERPRINT); + final byte [] cryptoClone = Arrays.copyOf(cryptoToken, cryptoToken.length); mHandler.post(new Runnable() { @Override public void run() { - startEnrollment(token, opid, groupId, receiver, flags); + startEnrollment(token, cryptoClone, groupId, receiver, flags); } }); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 55dd911..bb53534 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -148,7 +148,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. - return timeout; + return timeout * 1000000L; // nanoseconds } } catch (RemoteException ex) { } diff --git a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp index a6cdbc4..17f86ca 100644 --- a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp +++ b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp @@ -128,11 +128,16 @@ static void nativeInit(JNIEnv *env, jobject clazz, jobject mQueue, jobject callb gLooper = android_os_MessageQueue_getMessageQueue(env, mQueue)->getLooper(); } -static jint nativeEnroll(JNIEnv* env, jobject clazz, jint groupId, jint timeout) { - hw_auth_token_t *hat = NULL; // This is here as a placeholder, - // please figure out your favorite way to send the hat struct through JNI +static jint nativeEnroll(JNIEnv* env, jobject clazz, jbyteArray token, jint groupId, jint timeout) { ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll(gid=%d, timeout=%d)\n", groupId, timeout); - int ret = gContext.device->enroll(gContext.device, hat, groupId, timeout); + const int tokenSize = env->GetArrayLength(token); + jbyte* tokenData = env->GetByteArrayElements(token, 0); + if (tokenSize != sizeof(hw_auth_token_t)) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll() : invalid token size %d\n", tokenSize); + return -1; + } + int ret = gContext.device->enroll(gContext.device, (hw_auth_token_t*) tokenData, groupId, timeout); + env->ReleaseByteArrayElements(token, tokenData, 0); return reinterpret_cast<jint>(ret); } @@ -154,7 +159,7 @@ static jint nativeAuthenticate(JNIEnv* env, jobject clazz, jlong sessionId, jint return reinterpret_cast<jint>(ret); } -static jint nativeStopAuthentication(JNIEnv* env, jobject clazz, jlong sessionId) { +static jint nativeStopAuthentication(JNIEnv* env, jobject clazz) { ALOG(LOG_VERBOSE, LOG_TAG, "nativeStopAuthentication()\n"); int ret = gContext.device->cancel(gContext.device); return reinterpret_cast<jint>(ret); @@ -227,8 +232,8 @@ static jint nativeCloseHal(JNIEnv* env, jobject clazz) { // TODO: clean up void methods static const JNINativeMethod g_methods[] = { { "nativeAuthenticate", "(JI)I", (void*)nativeAuthenticate }, - { "nativeStopAuthentication", "(J)I", (void*)nativeStopAuthentication }, - { "nativeEnroll", "(JII)I", (void*)nativeEnroll }, + { "nativeStopAuthentication", "()I", (void*)nativeStopAuthentication }, + { "nativeEnroll", "([BII)I", (void*)nativeEnroll }, { "nativePreEnroll", "()J", (void*)nativePreEnroll }, { "nativeStopEnrollment", "()I", (void*)nativeStopEnrollment }, { "nativeRemove", "(II)I", (void*)nativeRemove }, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c5e1933..7e59943 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5817,12 +5817,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userId = UserHandle.getCallingUserId(); LockPatternUtils utils = new LockPatternUtils(mContext); - // disallow disabling the keyguard if a password is currently set - if (!enabled && utils.isSecure(userId)) { - return false; - } long ident = Binder.clearCallingIdentity(); try { + // disallow disabling the keyguard if a password is currently set + if (!enabled && utils.isSecure(userId)) { + return false; + } utils.setLockScreenDisabled(!enabled, userId); } finally { Binder.restoreCallingIdentity(ident); |