diff options
50 files changed, 4636 insertions, 1590 deletions
diff --git a/api/current.txt b/api/current.txt index 7aea255..063f6da 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13187,6 +13187,7 @@ package android.media { ctor public RemoteControlClient(android.app.PendingIntent); ctor public RemoteControlClient(android.app.PendingIntent, android.os.Looper); method public android.media.RemoteControlClient.MetadataEditor editMetadata(boolean); + method public void setMetadataUpdateListener(android.media.RemoteControlClient.OnMetadataUpdateListener); method public void setOnGetPlaybackPositionListener(android.media.RemoteControlClient.OnGetPlaybackPositionListener); method public void setPlaybackPositionUpdateListener(android.media.RemoteControlClient.OnPlaybackPositionUpdateListener); method public void setPlaybackState(int); @@ -13199,6 +13200,7 @@ package android.media { field public static final int FLAG_KEY_MEDIA_PLAY_PAUSE = 8; // 0x8 field public static final int FLAG_KEY_MEDIA_POSITION_UPDATE = 256; // 0x100 field public static final int FLAG_KEY_MEDIA_PREVIOUS = 1; // 0x1 + field public static final int FLAG_KEY_MEDIA_RATING = 512; // 0x200 field public static final int FLAG_KEY_MEDIA_REWIND = 2; // 0x2 field public static final int FLAG_KEY_MEDIA_STOP = 32; // 0x20 field public static final int PLAYSTATE_BUFFERING = 8; // 0x8 @@ -13213,18 +13215,32 @@ package android.media { } public class RemoteControlClient.MetadataEditor { + method public synchronized void addEditableKey(int); method public synchronized void apply(); method public synchronized void clear(); + method public synchronized void clearEditableKeys(); method public synchronized android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException; method public synchronized android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException; method public synchronized android.media.RemoteControlClient.MetadataEditor putString(int, java.lang.String) throws java.lang.IllegalArgumentException; field public static final int BITMAP_KEY_ARTWORK = 100; // 0x64 + field public static final int LONG_KEY_RATING_BY_OTHERS = 102; // 0x66 + field public static final int LONG_KEY_RATING_BY_USER = 268435457; // 0x10000001 + field public static final int LONG_KEY_RATING_TYPE = 101; // 0x65 + field public static final long RATING_HEART = -1L; // 0xffffffffffffffffL + field public static final long RATING_NOT_RATED = -101L; // 0xffffffffffffff9bL + field public static final long RATING_THUMB_UP_DOWN = -2L; // 0xfffffffffffffffeL } public static abstract interface RemoteControlClient.OnGetPlaybackPositionListener { method public abstract long onGetPlaybackPosition(); } + public static abstract interface RemoteControlClient.OnMetadataUpdateListener { + method public abstract void onMetadataUpdateBitmap(int, android.graphics.Bitmap); + method public abstract void onMetadataUpdateLong(int, long); + method public abstract void onMetadataUpdateString(int, java.lang.String); + } + public static abstract interface RemoteControlClient.OnPlaybackPositionUpdateListener { method public abstract void onPlaybackPositionUpdate(long); } @@ -15248,12 +15264,12 @@ package android.nfc { package android.nfc.cardemulation { - public final class CardEmulationManager { - method public static synchronized android.nfc.cardemulation.CardEmulationManager getInstance(android.nfc.NfcAdapter); + public final class CardEmulation { + method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter); method public int getSelectionModeForCategory(java.lang.String); method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String); method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String); - field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.ACTION_CHANGE_DEFAULT"; + field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; field public static final java.lang.String CATEGORY_OTHER = "other"; field public static final java.lang.String CATEGORY_PAYMENT = "payment"; field public static final java.lang.String EXTRA_CATEGORY = "category"; @@ -15273,15 +15289,15 @@ package android.nfc.cardemulation { method public final void sendResponseApdu(byte[]); field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 - field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.HostApduService"; - field public static final java.lang.String SERVICE_META_DATA = "android.nfc.HostApduService"; + field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; + field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } public abstract class OffHostApduService extends android.app.Service { ctor public OffHostApduService(); method public abstract android.os.IBinder onBind(android.content.Intent); - field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.OffHostApduService"; - field public static final java.lang.String SERVICE_META_DATA = "android.nfc.OffHostApduService"; + field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"; + field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service"; } } @@ -20815,67 +20831,70 @@ package android.provider { } public final class DocumentsContract { + method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String); method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String); - method public static java.lang.String getDocId(android.net.Uri); + method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String); + method public static android.net.Uri buildRootsUri(java.lang.String); + method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); + method public static java.lang.String getDocumentId(android.net.Uri); + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal); method public static android.net.Uri[] getOpenDocuments(android.content.Context); + method public static java.lang.String getRootId(android.net.Uri); + method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_LOADING = "loading"; } - public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns { - field public static final java.lang.String DOC_ID = "doc_id"; - field public static final java.lang.String FLAGS = "flags"; - field public static final java.lang.String ICON = "icon"; - field public static final java.lang.String LAST_MODIFIED = "last_modified"; - field public static final java.lang.String MIME_TYPE = "mime_type"; - field public static final java.lang.String SUMMARY = "summary"; - } - - public static final class DocumentsContract.DocumentRoot implements android.os.Parcelable { - ctor public DocumentsContract.DocumentRoot(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; + public static final class DocumentsContract.Document { + field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name"; + field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id"; + field public static final java.lang.String COLUMN_FLAGS = "flags"; + field public static final java.lang.String COLUMN_ICON = "icon"; + field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified"; + field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; + field public static final java.lang.String COLUMN_SIZE = "_size"; + field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20 + field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 + field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10 + field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 + field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 + field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; + } + + public static final class DocumentsContract.Root { + field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes"; + field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id"; + field public static final java.lang.String COLUMN_FLAGS = "flags"; + field public static final java.lang.String COLUMN_ICON = "icon"; + field public static final java.lang.String COLUMN_ROOT_ID = "root_id"; + field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type"; + field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final int FLAG_ADVANCED = 4; // 0x4 field public static final int FLAG_LOCAL_ONLY = 2; // 0x2 + field public static final int FLAG_PROVIDES_AUDIO = 8; // 0x8 + field public static final int FLAG_PROVIDES_IMAGES = 32; // 0x20 + field public static final int FLAG_PROVIDES_VIDEO = 16; // 0x10 field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 + field public static final int FLAG_SUPPORTS_RECENTS = 64; // 0x40 field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 - field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4 field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 - field public long availableBytes; - field public java.lang.String docId; - field public int flags; - field public int icon; - field public java.lang.String[] mimeTypes; - field public java.lang.String recentDocId; - field public int rootType; - field public java.lang.String summary; - field public java.lang.String title; - } - - public static final class DocumentsContract.Documents { - field public static final int FLAG_PREFERS_GRID = 64; // 0x40 - field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 - field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 - field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2 - field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 - field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8 - field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20 - field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.doc/dir"; } public abstract class DocumentsProvider extends android.content.ContentProvider { ctor public DocumentsProvider(); - method public final android.os.Bundle callFromPackage(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle); method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; - method public abstract java.util.List<android.provider.DocumentsContract.DocumentRoot> getDocumentRoots(); - method public java.lang.String getType(java.lang.String) throws java.io.FileNotFoundException; + method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException; method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public void notifyDocumentRootsChanged(); method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; @@ -20883,10 +20902,11 @@ package android.provider { method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public abstract android.database.Cursor queryDocument(java.lang.String) throws java.io.FileNotFoundException; - method public abstract android.database.Cursor queryDocumentChildren(java.lang.String) throws java.io.FileNotFoundException; - method public android.database.Cursor querySearch(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; - method public void renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } @@ -22573,8 +22593,11 @@ package android.security { } public final class KeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec { + method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec(); method public android.content.Context getContext(); method public java.util.Date getEndDate(); + method public int getKeySize(); + method public java.lang.String getKeyType(); method public java.lang.String getKeystoreAlias(); method public java.math.BigInteger getSerialNumber(); method public java.util.Date getStartDate(); @@ -22585,9 +22608,12 @@ package android.security { public static final class KeyPairGeneratorSpec.Builder { ctor public KeyPairGeneratorSpec.Builder(android.content.Context); method public android.security.KeyPairGeneratorSpec build(); + method public android.security.KeyPairGeneratorSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec); method public android.security.KeyPairGeneratorSpec.Builder setAlias(java.lang.String); method public android.security.KeyPairGeneratorSpec.Builder setEncryptionRequired(); method public android.security.KeyPairGeneratorSpec.Builder setEndDate(java.util.Date); + method public android.security.KeyPairGeneratorSpec.Builder setKeySize(int); + method public android.security.KeyPairGeneratorSpec.Builder setKeyType(java.lang.String) throws java.security.NoSuchAlgorithmException; method public android.security.KeyPairGeneratorSpec.Builder setSerialNumber(java.math.BigInteger); method public android.security.KeyPairGeneratorSpec.Builder setStartDate(java.util.Date); method public android.security.KeyPairGeneratorSpec.Builder setSubject(javax.security.auth.x500.X500Principal); @@ -27149,6 +27175,7 @@ package android.view { method public long getTimeDelta(); method public boolean isInProgress(); method public boolean onTouchEvent(android.view.MotionEvent); + method public void setQuickScaleEnabled(boolean); } public static abstract interface ScaleGestureDetector.OnScaleGestureListener { diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index b83911a..41c6603 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -105,8 +105,12 @@ public final class ApduServiceInfo implements Parcelable { if (onHost) { parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA); if (parser == null) { - throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA + - " meta-data"); + Log.d(TAG, "Didn't find service meta-data, trying legacy."); + parser = si.loadXmlMetaData(pm, HostApduService.OLD_SERVICE_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA + + " meta-data"); + } } } else { parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA); @@ -170,12 +174,12 @@ public final class ApduServiceInfo implements Parcelable { com.android.internal.R.styleable.AidGroup_description); String groupCategory = groupAttrs.getString( com.android.internal.R.styleable.AidGroup_category); - if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) { - groupCategory = CardEmulationManager.CATEGORY_OTHER; + if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) { + groupCategory = CardEmulation.CATEGORY_OTHER; } currentGroup = mCategoryToGroup.get(groupCategory); if (currentGroup != null) { - if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) { + if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) { Log.e(TAG, "Not allowing multiple aid-groups in the " + groupCategory + " category"); currentGroup = null; diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java new file mode 100644 index 0000000..3cd7863 --- /dev/null +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc.cardemulation; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.ActivityThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.nfc.INfcCardEmulation; +import android.nfc.NfcAdapter; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import java.util.HashMap; +import java.util.List; + +public final class CardEmulation { + static final String TAG = "CardEmulation"; + + /** + * Activity action: ask the user to change the default + * card emulation service for a certain category. This will + * show a dialog that asks the user whether he wants to + * replace the current default service with the service + * identified with the ComponentName specified in + * {@link #EXTRA_SERVICE_COMPONENT}, for the category + * specified in {@link #EXTRA_CATEGORY} + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHANGE_DEFAULT = + "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; + + /** + * The category extra for {@link #ACTION_CHANGE_DEFAULT} + * + * @see #ACTION_CHANGE_DEFAULT + */ + public static final String EXTRA_CATEGORY = "category"; + + /** + * The ComponentName object passed in as a parcelable + * extra for {@link #ACTION_CHANGE_DEFAULT} + * + * @see #ACTION_CHANGE_DEFAULT + */ + public static final String EXTRA_SERVICE_COMPONENT = "component"; + + /** + * The payment category can be used to indicate that an AID + * represents a payment application. + */ + public static final String CATEGORY_PAYMENT = "payment"; + + /** + * If an AID group does not contain a category, or the + * specified category is not defined by the platform version + * that is parsing the AID group, all AIDs in the group will + * automatically be categorized under the {@link #CATEGORY_OTHER} + * category. + */ + public static final String CATEGORY_OTHER = "other"; + + /** + * Return value for {@link #getSelectionModeForCategory(String)}. + * + * <p>In this mode, the user has set a default service for this + * AID category. If a remote reader selects any of the AIDs + * that the default service has registered in this category, + * that service will automatically be bound to to handle + * the transaction. + * + * <p>There are still cases where a service that is + * not the default for a category can selected: + * <p> + * If a remote reader selects an AID in this category + * that is not handled by the default service, and there is a set + * of other services {S} that do handle this AID, the + * user is asked if he wants to use any of the services in + * {S} instead. + * <p> + * As a special case, if the size of {S} is one, containing a single service X, + * and all AIDs X has registered in this category are not + * registered by any other service, then X will be + * selected automatically without asking the user. + * <p>Example: + * <ul> + * <li>Service A registers AIDs "1", "2" and "3" in the category + * <li>Service B registers AIDs "3" and "4" in the category + * <li>Service C registers AIDs "5" and "6" in the category + * </ul> + * In this case, the following will happen when service A + * is the default: + * <ul> + * <li>Reader selects AID "1", "2" or "3": service A is invoked automatically + * <li>Reader selects AID "4": the user is asked to confirm he + * wants to use service B, because its AIDs overlap with service A. + * <li>Reader selects AID "5" or "6": service C is invoked automatically, + * because all AIDs it has asked for are only registered by C, + * and there is no overlap. + * </ul> + * + */ + public static final int SELECTION_MODE_PREFER_DEFAULT = 0; + + /** + * Return value for {@link #getSelectionModeForCategory(String)}. + * + * <p>In this mode, whenever an AID of this category is selected, + * the user is asked which service he wants to use to handle + * the transaction, even if there is only one matching service. + */ + public static final int SELECTION_MODE_ALWAYS_ASK = 1; + + /** + * Return value for {@link #getSelectionModeForCategory(String)}. + * + * <p>In this mode, the user will only be asked to select a service + * if the selected AID has been registered by multiple applications. + */ + public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; + + static boolean sIsInitialized = false; + static HashMap<Context, CardEmulation> sCardEmus = new HashMap(); + static INfcCardEmulation sService; + + final Context mContext; + + private CardEmulation(Context context, INfcCardEmulation service) { + mContext = context.getApplicationContext(); + sService = service; + } + + public static synchronized CardEmulation getInstance(NfcAdapter adapter) { + if (adapter == null) throw new NullPointerException("NfcAdapter is null"); + Context context = adapter.getContext(); + if (context == null) { + Log.e(TAG, "NfcAdapter context is null."); + throw new UnsupportedOperationException(); + } + if (!sIsInitialized) { + IPackageManager pm = ActivityThread.getPackageManager(); + if (pm == null) { + Log.e(TAG, "Cannot get PackageManager"); + throw new UnsupportedOperationException(); + } + try { + if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { + Log.e(TAG, "This device does not support card emulation"); + throw new UnsupportedOperationException(); + } + } catch (RemoteException e) { + Log.e(TAG, "PackageManager query failed."); + throw new UnsupportedOperationException(); + } + sIsInitialized = true; + } + CardEmulation manager = sCardEmus.get(context); + if (manager == null) { + // Get card emu service + INfcCardEmulation service = adapter.getCardEmulationService(); + manager = new CardEmulation(context, service); + sCardEmus.put(context, manager); + } + return manager; + } + + /** + * Allows an application to query whether a service is currently + * the default service to handle a card emulation category. + * + * <p>Note that if {@link #getSelectionModeForCategory(String)} + * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always + * return false. + * + * @param service The ComponentName of the service + * @param category The category + * @return whether service is currently the default service for the category. + */ + public boolean isDefaultServiceForCategory(ComponentName service, String category) { + try { + return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, + category); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + } + } + + /** + * + * Allows an application to query whether a service is currently + * the default handler for a specified ISO7816-4 Application ID. + * + * @param service The ComponentName of the service + * @param aid The ISO7816-4 Application ID + * @return + */ + public boolean isDefaultServiceForAid(ComponentName service, String aid) { + try { + return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * Returns the application selection mode for the passed in category. + * Valid return values are: + * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default + * application for this category, which will be preferred. + * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked + * every time what app he would like to use in this category. + * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked + * to pick a service if there is a conflict. + * @param category The category, for example {@link #CATEGORY_PAYMENT} + * @return + */ + public int getSelectionModeForCategory(String category) { + if (CATEGORY_PAYMENT.equals(category)) { + String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); + if (defaultComponent != null) { + return SELECTION_MODE_PREFER_DEFAULT; + } else { + return SELECTION_MODE_ALWAYS_ASK; + } + } else { + // All other categories are in "only ask if conflict" mode + return SELECTION_MODE_ASK_IF_CONFLICT; + } + } + + /** + * @hide + */ + public boolean setDefaultServiceForCategory(ComponentName service, String category) { + try { + return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, + category); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * @hide + */ + public boolean setDefaultForNextTap(ComponentName service) { + try { + return sService.setDefaultForNextTap(UserHandle.myUserId(), service); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.setDefaultForNextTap(UserHandle.myUserId(), service); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + /** + * @hide + */ + public List<ApduServiceInfo> getServices(String category) { + try { + return sService.getServices(UserHandle.myUserId(), category); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + try { + return sService.getServices(UserHandle.myUserId(), category); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return null; + } + } + } + + void recoverService() { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); + sService = adapter.getCardEmulationService(); + } +} diff --git a/core/java/android/nfc/cardemulation/CardEmulationManager.java b/core/java/android/nfc/cardemulation/CardEmulationManager.java index 9d60c73..124ea1c 100644 --- a/core/java/android/nfc/cardemulation/CardEmulationManager.java +++ b/core/java/android/nfc/cardemulation/CardEmulationManager.java @@ -33,6 +33,10 @@ import android.util.Log; import java.util.HashMap; import java.util.List; +/** + * TODO Remove when calling .apks are upgraded + * @hide + */ public final class CardEmulationManager { static final String TAG = "CardEmulationManager"; diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index ae94b2f..1bb2ea4 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -40,13 +40,31 @@ public abstract class HostApduService extends Service { */ @SdkConstant(SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = + "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; + + /** + * The name of the meta-data element that contains + * more information about this service. + */ + public static final String SERVICE_META_DATA = + "android.nfc.cardemulation.host_apdu_service"; + + /** + * The {@link Intent} that must be declared as handled by the service. + * TODO Remove + * @hide + */ + public static final String OLD_SERVICE_INTERFACE = "android.nfc.HostApduService"; /** * The name of the meta-data element that contains * more information about this service. + * + * TODO Remove + * @hide */ - public static final String SERVICE_META_DATA = "android.nfc.HostApduService"; + public static final String OLD_SERVICE_META_DATA = "android.nfc.HostApduService"; /** * Reason for {@link #onDeactivated(int)}. diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java index 79599db..15f63f9 100644 --- a/core/java/android/nfc/cardemulation/OffHostApduService.java +++ b/core/java/android/nfc/cardemulation/OffHostApduService.java @@ -42,13 +42,14 @@ public abstract class OffHostApduService extends Service { */ @SdkConstant(SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = - "android.nfc.OffHostApduService"; + "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"; /** * The name of the meta-data element that contains * more information about this service. */ - public static final String SERVICE_META_DATA = "android.nfc.OffHostApduService"; + public static final String SERVICE_META_DATA = + "android.nfc.cardemulation.off_host_apdu_service"; /** * The Android platform itself will not bind to this service, diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index ebb7eb8..f445fd5 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -19,7 +19,6 @@ package android.provider; import static android.net.TrafficStats.KB_IN_BYTES; import static libcore.io.OsConstants.SEEK_SET; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -30,16 +29,13 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.os.Parcel; +import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; -import android.os.Parcelable; import android.util.Log; -import com.android.internal.util.Preconditions; import com.google.android.collect.Lists; import libcore.io.ErrnoException; @@ -62,9 +58,12 @@ import java.util.List; public final class DocumentsContract { private static final String TAG = "Documents"; - // content://com.example/docs/12/ - // content://com.example/docs/12/children/ - // content://com.example/docs/12/search/?query=pony + // content://com.example/root/ + // content://com.example/root/sdcard/ + // content://com.example/root/sdcard/recent/ + // content://com.example/document/12/ + // content://com.example/document/12/children/ + // content://com.example/document/12/search/?query=pony private DocumentsContract() { } @@ -75,279 +74,297 @@ public final class DocumentsContract { /** {@hide} */ public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS"; - /** {@hide} */ - public static final String - ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED"; - /** - * Constants for individual documents. + * Constants related to a document, including {@link Cursor} columns names + * and flags. + * <p> + * A document can be either an openable file (with a specific MIME type), or + * a directory containing additional documents (with the + * {@link #MIME_TYPE_DIR} MIME type). + * <p> + * All columns are <em>read-only</em> to client applications. */ - public final static class Documents { - private Documents() { + public final static class Document { + private Document() { } /** - * MIME type of a document which is a directory that may contain additional - * documents. + * Unique ID of a document. This ID is both provided by and interpreted + * by a {@link DocumentsProvider}, and should be treated as an opaque + * value by client applications. + * <p> + * Each document must have a unique ID within a provider, but that + * single document may be included as a child of multiple directories. + * <p> + * A provider must always return durable IDs, since they will be used to + * issue long-term Uri permission grants when an application interacts + * with {@link Intent#ACTION_OPEN_DOCUMENT} and + * {@link Intent#ACTION_CREATE_DOCUMENT}. + * <p> + * Type: STRING */ - public static final String MIME_TYPE_DIR = "vnd.android.doc/dir"; + public static final String COLUMN_DOCUMENT_ID = "document_id"; /** - * Flag indicating that a document is a directory that supports creation of - * new files within it. + * Concrete MIME type of a document. For example, "image/png" or + * "application/pdf" for openable files. A document can also be a + * directory containing additional documents, which is represented with + * the {@link #MIME_TYPE_DIR} MIME type. + * <p> + * Type: STRING * - * @see DocumentColumns#FLAGS + * @see #MIME_TYPE_DIR */ - public static final int FLAG_SUPPORTS_CREATE = 1; + public static final String COLUMN_MIME_TYPE = "mime_type"; /** - * Flag indicating that a document is renamable. + * Display name of a document, used as the primary title displayed to a + * user. + * <p> + * Type: STRING + */ + public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; + + /** + * Summary of a document, which may be shown to a user. The summary may + * be {@code null}. + * <p> + * Type: STRING + */ + public static final String COLUMN_SUMMARY = "summary"; + + /** + * Timestamp when a document was last modified, in milliseconds since + * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A + * {@link DocumentsProvider} can update this field using events from + * {@link OnCloseListener} or other reliable + * {@link ParcelFileDescriptor} transports. + * <p> + * Type: INTEGER (long) * - * @see DocumentColumns#FLAGS + * @see System#currentTimeMillis() */ - public static final int FLAG_SUPPORTS_RENAME = 1 << 1; + public static final String COLUMN_LAST_MODIFIED = "last_modified"; /** - * Flag indicating that a document is deletable. + * Specific icon resource ID for a document, or {@code null} to use + * platform default icon based on {@link #COLUMN_MIME_TYPE}. + * <p> + * Type: INTEGER (int) + */ + public static final String COLUMN_ICON = "icon"; + + /** + * Flags that apply to a document. + * <p> + * Type: INTEGER (int) * - * @see DocumentColumns#FLAGS + * @see #FLAG_SUPPORTS_WRITE + * @see #FLAG_SUPPORTS_DELETE + * @see #FLAG_SUPPORTS_THUMBNAIL + * @see #FLAG_DIR_PREFERS_GRID + * @see #FLAG_DIR_SUPPORTS_CREATE + * @see #FLAG_DIR_SUPPORTS_SEARCH */ - public static final int FLAG_SUPPORTS_DELETE = 1 << 2; + public static final String COLUMN_FLAGS = "flags"; /** - * Flag indicating that a document can be represented as a thumbnail. + * Size of a document, in bytes, or {@code null} if unknown. + * <p> + * Type: INTEGER (long) + */ + public static final String COLUMN_SIZE = OpenableColumns.SIZE; + + /** + * MIME type of a document which is a directory that may contain + * additional documents. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_MIME_TYPE */ - public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; + public static final String MIME_TYPE_DIR = "vnd.android.document/directory"; /** - * Flag indicating that a document is a directory that supports search. + * Flag indicating that a document can be represented as a thumbnail. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_FLAGS + * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri, + * Point, CancellationSignal) + * @see DocumentsProvider#openDocumentThumbnail(String, Point, + * android.os.CancellationSignal) */ - public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + public static final int FLAG_SUPPORTS_THUMBNAIL = 1; /** * Flag indicating that a document supports writing. + * <p> + * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT}, + * the calling application is granted both + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual + * writability of a document may change over time, for example due to + * remote access changes. This flag indicates that a document client can + * expect {@link ContentResolver#openOutputStream(Uri)} to succeed. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_FLAGS */ - public static final int FLAG_SUPPORTS_WRITE = 1 << 5; + public static final int FLAG_SUPPORTS_WRITE = 1 << 1; /** - * Flag indicating that a document is a directory that prefers its contents - * be shown in a larger format grid. Usually suitable when a directory - * contains mostly pictures. + * Flag indicating that a document is deletable. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_FLAGS + * @see DocumentsContract#deleteDocument(ContentResolver, Uri) + * @see DocumentsProvider#deleteDocument(String) */ - public static final int FLAG_PREFERS_GRID = 1 << 6; - } - - /** - * Extra boolean flag included in a directory {@link Cursor#getExtras()} - * indicating that a document provider is still loading data. For example, a - * provider has returned some results, but is still waiting on an - * outstanding network request. - * - * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, - * boolean) - */ - public static final String EXTRA_LOADING = "loading"; - - /** - * Extra string included in a directory {@link Cursor#getExtras()} - * providing an informational message that should be shown to a user. For - * example, a provider may wish to indicate that not all documents are - * available. - */ - public static final String EXTRA_INFO = "info"; - - /** - * Extra string included in a directory {@link Cursor#getExtras()} providing - * an error message that should be shown to a user. For example, a provider - * may wish to indicate that a network error occurred. The user may choose - * to retry, resulting in a new query. - */ - public static final String EXTRA_ERROR = "error"; - - /** {@hide} */ - public static final String METHOD_GET_ROOTS = "android:getRoots"; - /** {@hide} */ - public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; - /** {@hide} */ - public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; - /** {@hide} */ - public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; - - /** {@hide} */ - public static final String EXTRA_AUTHORITY = "authority"; - /** {@hide} */ - public static final String EXTRA_PACKAGE_NAME = "packageName"; - /** {@hide} */ - public static final String EXTRA_URI = "uri"; - /** {@hide} */ - public static final String EXTRA_ROOTS = "roots"; - /** {@hide} */ - public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - - private static final String PATH_DOCS = "docs"; - private static final String PATH_CHILDREN = "children"; - private static final String PATH_SEARCH = "search"; - - private static final String PARAM_QUERY = "query"; + public static final int FLAG_SUPPORTS_DELETE = 1 << 2; - /** - * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a - * document provider. - */ - public static Uri buildDocumentUri(String authority, String docId) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(authority).appendPath(PATH_DOCS).appendPath(docId).build(); - } + /** + * Flag indicating that a document is a directory that supports creation + * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is + * {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#createDocument(ContentResolver, Uri, String, + * String) + * @see DocumentsProvider#createDocument(String, String, String) + */ + public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3; - /** - * Build Uri representing the contents of the given directory in a document - * provider. The given document must be {@link Documents#MIME_TYPE_DIR}. - * - * @hide - */ - public static Uri buildChildrenUri(String authority, String docId) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) - .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build(); - } + /** + * Flag indicating that a directory supports search. Only valid when + * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + * @see DocumentsProvider#querySearchDocuments(String, String, + * String[]) + */ + public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4; - /** - * Build Uri representing a search for matching documents under a specific - * directory in a document provider. The given document must have - * {@link Documents#FLAG_SUPPORTS_SEARCH}. - * - * @hide - */ - public static Uri buildSearchUri(String authority, String docId, String query) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) - .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH) - .appendQueryParameter(PARAM_QUERY, query).build(); + /** + * Flag indicating that a directory prefers its contents be shown in a + * larger format grid. Usually suitable when a directory contains mostly + * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is + * {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + */ + public static final int FLAG_DIR_PREFERS_GRID = 1 << 5; } /** - * Extract the {@link DocumentColumns#DOC_ID} from the given Uri. + * Constants related to a root of documents, including {@link Cursor} + * columns names and flags. + * <p> + * All columns are <em>read-only</em> to client applications. */ - public static String getDocId(Uri documentUri) { - final List<String> paths = documentUri.getPathSegments(); - if (paths.size() < 2) { - throw new IllegalArgumentException("Not a document: " + documentUri); + public final static class Root { + private Root() { } - if (!PATH_DOCS.equals(paths.get(0))) { - throw new IllegalArgumentException("Not a document: " + documentUri); - } - return paths.get(1); - } - - /** {@hide} */ - public static String getSearchQuery(Uri documentUri) { - return documentUri.getQueryParameter(PARAM_QUERY); - } - /** - * Standard columns for document queries. Document providers <em>must</em> - * support at least these columns when queried. - */ - public interface DocumentColumns extends OpenableColumns { /** - * Unique ID for a document. Values <em>must</em> never change once - * returned, since they may used for long-term Uri permission grants. + * Unique ID of a root. This ID is both provided by and interpreted by a + * {@link DocumentsProvider}, and should be treated as an opaque value + * by client applications. * <p> * Type: STRING */ - public static final String DOC_ID = "doc_id"; + public static final String COLUMN_ROOT_ID = "root_id"; /** - * MIME type of a document. + * Type of a root, used for clustering when presenting multiple roots to + * a user. * <p> - * Type: STRING + * Type: INTEGER (int) * - * @see Documents#MIME_TYPE_DIR + * @see #ROOT_TYPE_SERVICE + * @see #ROOT_TYPE_SHORTCUT + * @see #ROOT_TYPE_DEVICE */ - public static final String MIME_TYPE = "mime_type"; + public static final String COLUMN_ROOT_TYPE = "root_type"; /** - * Timestamp when a document was last modified, in milliseconds since - * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document - * providers can update this field using events from - * {@link OnCloseListener} or other reliable - * {@link ParcelFileDescriptor} transports. + * Flags that apply to a root. * <p> - * Type: INTEGER (long) + * Type: INTEGER (int) * - * @see System#currentTimeMillis() + * @see #FLAG_LOCAL_ONLY + * @see #FLAG_SUPPORTS_CREATE + * @see #FLAG_ADVANCED + * @see #FLAG_PROVIDES_AUDIO + * @see #FLAG_PROVIDES_IMAGES + * @see #FLAG_PROVIDES_VIDEO */ - public static final String LAST_MODIFIED = "last_modified"; + public static final String COLUMN_FLAGS = "flags"; /** - * Specific icon resource for a document, or {@code null} to resolve - * default using {@link #MIME_TYPE}. + * Icon resource ID for a root. * <p> * Type: INTEGER (int) */ - public static final String ICON = "icon"; + public static final String COLUMN_ICON = "icon"; /** - * Summary for a document, or {@code null} to omit. + * Title for a root, which will be shown to a user. * <p> * Type: STRING */ - public static final String SUMMARY = "summary"; + public static final String COLUMN_TITLE = "title"; /** - * Flags that apply to a specific document. + * Summary for this root, which may be shown to a user. The summary may + * be {@code null}. * <p> - * Type: INTEGER (int) + * Type: STRING */ - public static final String FLAGS = "flags"; - } + public static final String COLUMN_SUMMARY = "summary"; - /** - * Metadata about a specific root of documents. - */ - public final static class DocumentRoot implements Parcelable { /** - * Root that represents a storage service, such as a cloud-based - * service. + * Document which is a directory that represents the top directory of + * this root. + * <p> + * Type: STRING * - * @see #rootType + * @see Document#COLUMN_DOCUMENT_ID */ - public static final int ROOT_TYPE_SERVICE = 1; + public static final String COLUMN_DOCUMENT_ID = "document_id"; + + /** + * Number of bytes available in this root, or {@code null} if unknown or + * unbounded. + * <p> + * Type: INTEGER (long) + */ + public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; /** - * Root that represents a shortcut to content that may be available - * elsewhere through another storage root. + * Type of root that represents a storage service, such as a cloud-based + * service. * - * @see #rootType + * @see #COLUMN_ROOT_TYPE */ - public static final int ROOT_TYPE_SHORTCUT = 2; + public static final int ROOT_TYPE_SERVICE = 1; /** - * Root that represents a physical storage device. + * Type of root that represents a shortcut to content that may be + * available elsewhere through another storage root. * - * @see #rootType + * @see #COLUMN_ROOT_TYPE */ - public static final int ROOT_TYPE_DEVICE = 3; + public static final int ROOT_TYPE_SHORTCUT = 2; /** - * Root that represents a physical storage device that should only be - * displayed to advanced users. + * Type of root that represents a physical storage device. * - * @see #rootType + * @see #COLUMN_ROOT_TYPE */ - public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; + public static final int ROOT_TYPE_DEVICE = 3; /** * Flag indicating that at least one directory under this root supports - * creating content. + * creating content. Roots with this flag will be shown when an + * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}. * - * @see #flags + * @see #COLUMN_FLAGS */ public static final int FLAG_SUPPORTS_CREATE = 1; @@ -355,138 +372,210 @@ public final class DocumentsContract { * Flag indicating that this root offers content that is strictly local * on the device. That is, no network requests are made for the content. * - * @see #flags + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_LOCAL_ONLY */ public static final int FLAG_LOCAL_ONLY = 1 << 1; - /** {@hide} */ - public String authority; - /** - * Root type, use for clustering. + * Flag indicating that this root should only be visible to advanced + * users. * - * @see #ROOT_TYPE_SERVICE - * @see #ROOT_TYPE_DEVICE + * @see #COLUMN_FLAGS */ - public int rootType; + public static final int FLAG_ADVANCED = 1 << 2; /** - * Flags for this root. + * Flag indicating that a root offers audio documents. When a user is + * selecting audio, roots not providing audio may be excluded. * - * @see #FLAG_LOCAL_ONLY + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_MIME_TYPES */ - public int flags; + public static final int FLAG_PROVIDES_AUDIO = 1 << 3; /** - * Icon resource ID for this root. - */ - public int icon; - - /** - * Title for this root. - */ - public String title; - - /** - * Summary for this root. May be {@code null}. + * Flag indicating that a root offers video documents. When a user is + * selecting video, roots not providing video may be excluded. + * + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_MIME_TYPES */ - public String summary; + public static final int FLAG_PROVIDES_VIDEO = 1 << 4; /** - * Document which is a directory that represents the top of this root. - * Must not be {@code null}. + * Flag indicating that a root offers image documents. When a user is + * selecting images, roots not providing images may be excluded. * - * @see DocumentColumns#DOC_ID + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_MIME_TYPES */ - public String docId; + public static final int FLAG_PROVIDES_IMAGES = 1 << 5; /** - * Document which is a directory representing recently modified - * documents under this root. This directory should return at most two - * dozen documents modified within the last 90 days. May be {@code null} - * if this root doesn't support recents. + * Flag indicating that this root can report recently modified + * documents. * - * @see DocumentColumns#DOC_ID + * @see #COLUMN_FLAGS + * @see DocumentsContract#buildRecentDocumentsUri(String, String) */ - public String recentDocId; + public static final int FLAG_SUPPORTS_RECENTS = 1 << 6; + } - /** - * Number of free bytes of available in this root, or -1 if unknown or - * unbounded. - */ - public long availableBytes; + /** + * Optional boolean flag included in a directory {@link Cursor#getExtras()} + * indicating that a document provider is still loading data. For example, a + * provider has returned some results, but is still waiting on an + * outstanding network request. The provider must send a content changed + * notification when loading is finished. + * + * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, + * boolean) + */ + public static final String EXTRA_LOADING = "loading"; - /** - * Set of MIME type filters describing the content offered by this root, - * or {@code null} to indicate that all MIME types are supported. For - * example, a provider only supporting audio and video might set this to - * {@code ["audio/*", "video/*"]}. - */ - public String[] mimeTypes; + /** + * Optional string included in a directory {@link Cursor#getExtras()} + * providing an informational message that should be shown to a user. For + * example, a provider may wish to indicate that not all documents are + * available. + */ + public static final String EXTRA_INFO = "info"; - public DocumentRoot() { - } + /** + * Optional string included in a directory {@link Cursor#getExtras()} + * providing an error message that should be shown to a user. For example, a + * provider may wish to indicate that a network error occurred. The user may + * choose to retry, resulting in a new query. + */ + public static final String EXTRA_ERROR = "error"; - /** {@hide} */ - public DocumentRoot(Parcel in) { - rootType = in.readInt(); - flags = in.readInt(); - icon = in.readInt(); - title = in.readString(); - summary = in.readString(); - docId = in.readString(); - recentDocId = in.readString(); - availableBytes = in.readLong(); - mimeTypes = in.readStringArray(); - } + /** {@hide} */ + public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; + /** {@hide} */ + public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; - /** {@hide} */ - public Drawable loadIcon(Context context) { - if (icon != 0) { - if (authority != null) { - final PackageManager pm = context.getPackageManager(); - final ProviderInfo info = pm.resolveContentProvider(authority, 0); - if (info != null) { - return pm.getDrawable(info.packageName, icon, info.applicationInfo); - } - } else { - return context.getResources().getDrawable(icon); - } - } - return null; - } + /** {@hide} */ + public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - @Override - public int describeContents() { - return 0; - } + private static final String PATH_ROOT = "root"; + private static final String PATH_RECENT = "recent"; + private static final String PATH_DOCUMENT = "document"; + private static final String PATH_CHILDREN = "children"; + private static final String PATH_SEARCH = "search"; - @Override - public void writeToParcel(Parcel dest, int flags) { - Preconditions.checkNotNull(docId); - - dest.writeInt(rootType); - dest.writeInt(flags); - dest.writeInt(icon); - dest.writeString(title); - dest.writeString(summary); - dest.writeString(docId); - dest.writeString(recentDocId); - dest.writeLong(availableBytes); - dest.writeStringArray(mimeTypes); + private static final String PARAM_QUERY = "query"; + + /** + * Build Uri representing the roots of a document provider. When queried, a + * provider will return one or more rows with columns defined by + * {@link Root}. + * + * @see DocumentsProvider#queryRoots(String[]) + */ + public static Uri buildRootsUri(String authority) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_ROOT).build(); + } + + /** + * Build Uri representing the recently modified documents of a specific + * root. When queried, a provider will return zero or more rows with columns + * defined by {@link Document}. + * + * @see DocumentsProvider#queryRecentDocuments(String, String[]) + * @see #getRootId(Uri) + */ + public static Uri buildRecentDocumentsUri(String authority, String rootId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) + .appendPath(PATH_RECENT).build(); + } + + /** + * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a + * document provider. When queried, a provider will return a single row with + * columns defined by {@link Document}. + * + * @see DocumentsProvider#queryDocument(String, String[]) + * @see #getDocumentId(Uri) + */ + public static Uri buildDocumentUri(String authority, String documentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); + } + + /** + * Build Uri representing the children of the given directory in a document + * provider. When queried, a provider will return zero or more rows with + * columns defined by {@link Document}. + * + * @param parentDocumentId the document to return children for, which must + * be a directory with MIME type of + * {@link Document#MIME_TYPE_DIR}. + * @see DocumentsProvider#queryChildDocuments(String, String[], String) + * @see #getDocumentId(Uri) + */ + public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) + .build(); + } + + /** + * Build Uri representing a search for matching documents under a specific + * directory in a document provider. When queried, a provider will return + * zero or more rows with columns defined by {@link Document}. + * + * @param parentDocumentId the document to return children for, which must + * be both a directory with MIME type of + * {@link Document#MIME_TYPE_DIR} and have + * {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set. + * @see DocumentsProvider#querySearchDocuments(String, String, String[]) + * @see #getDocumentId(Uri) + * @see #getSearchDocumentsQuery(Uri) + */ + public static Uri buildSearchDocumentsUri( + String authority, String parentDocumentId, String query) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH) + .appendQueryParameter(PARAM_QUERY, query).build(); + } + + /** + * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri. + */ + public static String getRootId(Uri rootUri) { + final List<String> paths = rootUri.getPathSegments(); + if (paths.size() < 2) { + throw new IllegalArgumentException("Not a root: " + rootUri); + } + if (!PATH_ROOT.equals(paths.get(0))) { + throw new IllegalArgumentException("Not a root: " + rootUri); } + return paths.get(1); + } - public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() { - @Override - public DocumentRoot createFromParcel(Parcel in) { - return new DocumentRoot(in); - } + /** + * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri. + */ + public static String getDocumentId(Uri documentUri) { + final List<String> paths = documentUri.getPathSegments(); + if (paths.size() < 2) { + throw new IllegalArgumentException("Not a document: " + documentUri); + } + if (!PATH_DOCUMENT.equals(paths.get(0))) { + throw new IllegalArgumentException("Not a document: " + documentUri); + } + return paths.get(1); + } - @Override - public DocumentRoot[] newArray(int size) { - return new DocumentRoot[size]; - } - }; + /** + * Extract the search query from a Uri built by + * {@link #buildSearchDocumentsUri(String, String, String)}. + */ + public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { + return searchDocumentsUri.getQueryParameter(PARAM_QUERY); } /** @@ -497,6 +586,7 @@ public final class DocumentsContract { * {@link Intent#ACTION_CREATE_DOCUMENT}. * * @see Context#grantUriPermission(String, Uri, int) + * @see Context#revokeUriPermission(Uri, int) * @see ContentResolver#getIncomingUriPermissionGrants(int, int) */ public static Uri[] getOpenDocuments(Context context) { @@ -520,20 +610,28 @@ public final class DocumentsContract { } /** - * Return thumbnail representing the document at the given URI. Callers are - * responsible for their own in-memory caching. Given document must have - * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set. + * Return thumbnail representing the document at the given Uri. Callers are + * responsible for their own in-memory caching. * + * @param documentUri document to return thumbnail for, which must have + * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. + * @param size optimal thumbnail size desired. A provider may return a + * thumbnail of a different size, but never more than double the + * requested size. + * @param signal signal used to indicate that caller is no longer interested + * in the thumbnail. * @return decoded thumbnail, or {@code null} if problem was encountered. - * @hide + * @see DocumentsProvider#openDocumentThumbnail(String, Point, + * android.os.CancellationSignal) */ - public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) { + public static Bitmap getDocumentThumbnail( + ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { final Bundle openOpts = new Bundle(); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); AssetFileDescriptor afd = null; try { - afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts); + afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); final FileDescriptor fd = afd.getFileDescriptor(); final long offset = afd.getStartOffset(); @@ -583,38 +681,26 @@ public final class DocumentsContract { } } - /** {@hide} */ - public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) { - try { - final Bundle out = client.call(METHOD_GET_ROOTS, null, null); - final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS); - return roots; - } catch (Exception e) { - Log.w(TAG, "Failed to get roots", e); - return null; - } - } - /** - * Create a new document under the given parent document with MIME type and - * display name. + * Create a new document with given MIME type and display name. * - * @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE} + * @param parentDocumentUri directory with + * {@link Document#FLAG_DIR_SUPPORTS_CREATE} * @param mimeType MIME type of new document * @param displayName name of new document * @return newly created document, or {@code null} if failed - * @hide */ - public static String createDocument( - ContentProviderClient client, String docId, String mimeType, String displayName) { + public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, + String mimeType, String displayName) { final Bundle in = new Bundle(); - in.putString(DocumentColumns.DOC_ID, docId); - in.putString(DocumentColumns.MIME_TYPE, mimeType); - in.putString(DocumentColumns.DISPLAY_NAME, displayName); + in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); + in.putString(Document.COLUMN_MIME_TYPE, mimeType); + in.putString(Document.COLUMN_DISPLAY_NAME, displayName); try { - final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); - return out.getString(DocumentColumns.DOC_ID); + final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in); + return buildDocumentUri( + parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); } catch (Exception e) { Log.w(TAG, "Failed to create document", e); return null; @@ -622,40 +708,16 @@ public final class DocumentsContract { } /** - * Rename the given document. - * - * @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME} - * @return document which may have changed due to rename, or {@code null} if - * rename failed. - * @hide - */ - public static String renameDocument( - ContentProviderClient client, String docId, String displayName) { - final Bundle in = new Bundle(); - in.putString(DocumentColumns.DOC_ID, docId); - in.putString(DocumentColumns.DISPLAY_NAME, displayName); - - try { - final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); - return out.getString(DocumentColumns.DOC_ID); - } catch (Exception e) { - Log.w(TAG, "Failed to rename document", e); - return null; - } - } - - /** * Delete the given document. * - * @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE} - * @hide + * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} */ - public static boolean deleteDocument(ContentProviderClient client, String docId) { + public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { final Bundle in = new Bundle(); - in.putString(DocumentColumns.DOC_ID, docId); + in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); try { - client.call(METHOD_DELETE_DOCUMENT, null, in); + final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in); return true; } catch (Exception e) { Log.w(TAG, "Failed to delete document", e); diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index eeb8c41..09f4866 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -16,16 +16,12 @@ package android.provider; -import static android.provider.DocumentsContract.ACTION_DOCUMENT_ROOT_CHANGED; -import static android.provider.DocumentsContract.EXTRA_AUTHORITY; -import static android.provider.DocumentsContract.EXTRA_ROOTS; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; -import static android.provider.DocumentsContract.METHOD_GET_ROOTS; -import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; -import static android.provider.DocumentsContract.getDocId; -import static android.provider.DocumentsContract.getSearchQuery; +import static android.provider.DocumentsContract.getDocumentId; +import static android.provider.DocumentsContract.getRootId; +import static android.provider.DocumentsContract.getSearchDocumentsQuery; import android.content.ContentProvider; import android.content.ContentValues; @@ -41,15 +37,12 @@ import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.DocumentRoot; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; import android.util.Log; import libcore.io.IoUtils; import java.io.FileNotFoundException; -import java.util.List; /** * Base class for a document provider. A document provider should extend this @@ -58,13 +51,13 @@ import java.util.List; * Each document provider expresses one or more "roots" which each serve as the * top-level of a tree. For example, a root could represent an account, or a * physical storage device. Under each root, documents are referenced by - * {@link DocumentColumns#DOC_ID}, which must not change once returned. + * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. * <p> * Documents can be either an openable file (with a specific MIME type), or a * directory containing additional documents (with the - * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different - * capabilities, as described by {@link DocumentColumns#FLAGS}. The same - * {@link DocumentColumns#DOC_ID} can be included in multiple directories. + * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different + * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same + * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories. * <p> * Document providers must be protected with the * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can @@ -78,22 +71,29 @@ import java.util.List; public abstract class DocumentsProvider extends ContentProvider { private static final String TAG = "DocumentsProvider"; - private static final int MATCH_DOCUMENT = 1; - private static final int MATCH_CHILDREN = 2; - private static final int MATCH_SEARCH = 3; + private static final int MATCH_ROOT = 1; + private static final int MATCH_RECENT = 2; + private static final int MATCH_DOCUMENT = 3; + private static final int MATCH_CHILDREN = 4; + private static final int MATCH_SEARCH = 5; private String mAuthority; private UriMatcher mMatcher; + /** + * Implementation is provided by the parent class. + */ @Override public void attachInfo(Context context, ProviderInfo info) { mAuthority = info.authority; mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT); - mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN); - mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH); + mMatcher.addURI(mAuthority, "root", MATCH_ROOT); + mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); + mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); + mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); + mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH); // Sanity check our setup if (!info.exported) { @@ -111,83 +111,80 @@ public abstract class DocumentsProvider extends ContentProvider { } /** - * Return list of all document roots provided by this document provider. - * When this list changes, a provider must call - * {@link #notifyDocumentRootsChanged()}. - */ - public abstract List<DocumentRoot> getDocumentRoots(); - - /** - * Create and return a new document. A provider must allocate a new - * {@link DocumentColumns#DOC_ID} to represent the document, which must not - * change once returned. + * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}. + * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to + * represent the document, which must not change once returned. * - * @param docId the parent directory to create the new document under. + * @param documentId the parent directory to create the new document under. * @param mimeType the MIME type associated with the new document. * @param displayName the display name of the new document. */ @SuppressWarnings("unused") - public String createDocument(String docId, String mimeType, String displayName) + public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException { throw new UnsupportedOperationException("Create not supported"); } /** - * Rename the given document. + * Delete the given document. Upon returning, any Uri permission grants for + * the given document will be revoked. If additional documents were deleted + * as a side effect of this call, such as documents inside a directory, the + * implementor is responsible for revoking those permissions. * - * @param docId the document to rename. - * @param displayName the new display name. + * @param documentId the document to delete. */ @SuppressWarnings("unused") - public void renameDocument(String docId, String displayName) throws FileNotFoundException { - throw new UnsupportedOperationException("Rename not supported"); + public void deleteDocument(String documentId) throws FileNotFoundException { + throw new UnsupportedOperationException("Delete not supported"); } - /** - * Delete the given document. - * - * @param docId the document to delete. - */ + public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; + @SuppressWarnings("unused") - public void deleteDocument(String docId) throws FileNotFoundException { - throw new UnsupportedOperationException("Delete not supported"); + public Cursor queryRecentDocuments(String rootId, String[] projection) + throws FileNotFoundException { + throw new UnsupportedOperationException("Recent not supported"); } /** * Return metadata for the given document. A provider should avoid making * network requests to keep this request fast. * - * @param docId the document to return. + * @param documentId the document to return. */ - public abstract Cursor queryDocument(String docId) throws FileNotFoundException; + public abstract Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException; /** * Return the children of the given document which is a directory. * - * @param docId the directory to return children for. + * @param parentDocumentId the directory to return children for. */ - public abstract Cursor queryDocumentChildren(String docId) throws FileNotFoundException; + public abstract Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException; /** * Return documents that that match the given query, starting the search at * the given directory. * - * @param docId the directory to start search at. + * @param parentDocumentId the directory to start search at. */ @SuppressWarnings("unused") - public Cursor querySearch(String docId, String query) throws FileNotFoundException { + public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection) + throws FileNotFoundException { throw new UnsupportedOperationException("Search not supported"); } /** * Return MIME type for the given document. Must match the value of - * {@link DocumentColumns#MIME_TYPE} for this document. + * {@link Document#COLUMN_MIME_TYPE} for this document. */ - public String getType(String docId) throws FileNotFoundException { - final Cursor cursor = queryDocument(docId); + public String getDocumentType(String documentId) throws FileNotFoundException { + final Cursor cursor = queryDocument(documentId, null); try { if (cursor.moveToFirst()) { - return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE)); + return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); } else { return null; } @@ -233,7 +230,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @param sizeHint hint of the optimal thumbnail dimensions. * @param signal used by the caller to signal if the request should be * cancelled. - * @see Documents#FLAG_SUPPORTS_THUMBNAIL + * @see Document#FLAG_SUPPORTS_THUMBNAIL */ @SuppressWarnings("unused") public AssetFileDescriptor openDocumentThumbnail( @@ -241,17 +238,31 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Thumbnails not supported"); } + /** + * Implementation is provided by the parent class. Cannot be overriden. + * + * @see #queryRoots(String[]) + * @see #queryRecentDocuments(String, String[]) + * @see #queryDocument(String, String[]) + * @see #queryChildDocuments(String, String[], String) + * @see #querySearchDocuments(String, String, String[]) + */ @Override - public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + public final Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { try { switch (mMatcher.match(uri)) { + case MATCH_ROOT: + return queryRoots(projection); + case MATCH_RECENT: + return queryRecentDocuments(getRootId(uri), projection); case MATCH_DOCUMENT: - return queryDocument(getDocId(uri)); + return queryDocument(getDocumentId(uri), projection); case MATCH_CHILDREN: - return queryDocumentChildren(getDocId(uri)); + return queryChildDocuments(getDocumentId(uri), projection, sortOrder); case MATCH_SEARCH: - return querySearch(getDocId(uri), getSearchQuery(uri)); + return querySearchDocuments( + getDocumentId(uri), getSearchDocumentsQuery(uri), projection); default: throw new UnsupportedOperationException("Unsupported Uri " + uri); } @@ -261,12 +272,17 @@ public abstract class DocumentsProvider extends ContentProvider { } } + /** + * Implementation is provided by the parent class. Cannot be overriden. + * + * @see #getDocumentType(String) + */ @Override public final String getType(Uri uri) { try { switch (mMatcher.match(uri)) { case MATCH_DOCUMENT: - return getType(getDocId(uri)); + return getDocumentType(getDocumentId(uri)); default: return null; } @@ -276,22 +292,39 @@ public abstract class DocumentsProvider extends ContentProvider { } } + /** + * Implementation is provided by the parent class. Throws by default, and + * cannot be overriden. + * + * @see #createDocument(String, String, String) + */ @Override public final Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("Insert not supported"); } + /** + * Implementation is provided by the parent class. Throws by default, and + * cannot be overriden. + * + * @see #deleteDocument(String) + */ @Override public final int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Delete not supported"); } + /** + * Implementation is provided by the parent class. Throws by default, and + * cannot be overriden. + */ @Override public final int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Update not supported"); } + /** {@hide} */ @Override public final Bundle callFromPackage( String callingPackage, String method, String arg, Bundle extras) { @@ -300,33 +333,25 @@ public abstract class DocumentsProvider extends ContentProvider { return super.callFromPackage(callingPackage, method, arg, extras); } - // Platform operations require the caller explicitly hold manage - // permission; Uri permissions don't extend management operations. - getContext().enforceCallingOrSelfPermission( - android.Manifest.permission.MANAGE_DOCUMENTS, "Document management"); + // Require that caller can manage given document + final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); + final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); + getContext().enforceCallingOrSelfUriPermission( + documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); final Bundle out = new Bundle(); try { - if (METHOD_GET_ROOTS.equals(method)) { - final List<DocumentRoot> roots = getDocumentRoots(); - out.putParcelableList(EXTRA_ROOTS, roots); - - } else if (METHOD_CREATE_DOCUMENT.equals(method)) { - final String docId = extras.getString(DocumentColumns.DOC_ID); - final String mimeType = extras.getString(DocumentColumns.MIME_TYPE); - final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME); + if (METHOD_CREATE_DOCUMENT.equals(method)) { + final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); + final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); - // TODO: issue Uri grant towards caller - final String newDocId = createDocument(docId, mimeType, displayName); - out.putString(DocumentColumns.DOC_ID, newDocId); - - } else if (METHOD_RENAME_DOCUMENT.equals(method)) { - final String docId = extras.getString(DocumentColumns.DOC_ID); - final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME); - renameDocument(docId, displayName); + // TODO: issue Uri grant towards calling package + // TODO: enforce that package belongs to caller + final String newDocumentId = createDocument(documentId, mimeType, displayName); + out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); } else if (METHOD_DELETE_DOCUMENT.equals(method)) { - final String docId = extras.getString(DocumentColumns.DOC_ID); + final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID); deleteDocument(docId); } else { @@ -338,47 +363,57 @@ public abstract class DocumentsProvider extends ContentProvider { return out; } + /** + * Implementation is provided by the parent class. + * + * @see #openDocument(String, String, CancellationSignal) + */ @Override public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - return openDocument(getDocId(uri), mode, null); + return openDocument(getDocumentId(uri), mode, null); } + /** + * Implementation is provided by the parent class. + * + * @see #openDocument(String, String, CancellationSignal) + */ @Override public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException { - return openDocument(getDocId(uri), mode, signal); + return openDocument(getDocumentId(uri), mode, signal); } + /** + * Implementation is provided by the parent class. + * + * @see #openDocumentThumbnail(String, Point, CancellationSignal) + */ @Override public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) throws FileNotFoundException { if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); - return openDocumentThumbnail(getDocId(uri), sizeHint, null); + return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); } else { return super.openTypedAssetFile(uri, mimeTypeFilter, opts); } } + /** + * Implementation is provided by the parent class. + * + * @see #openDocumentThumbnail(String, Point, CancellationSignal) + */ @Override public final AssetFileDescriptor openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) throws FileNotFoundException { if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); - return openDocumentThumbnail(getDocId(uri), sizeHint, signal); + return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); } else { return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); } } - - /** - * Notify system that {@link #getDocumentRoots()} has changed, usually due to an - * account or device change. - */ - public void notifyDocumentRootsChanged() { - final Intent intent = new Intent(ACTION_DOCUMENT_ROOT_CHANGED); - intent.putExtra(EXTRA_AUTHORITY, mAuthority); - getContext().sendBroadcast(intent); - } } diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java index 3d75dc8..bf8d4e5 100644 --- a/core/java/android/security/IKeystoreService.java +++ b/core/java/android/security/IKeystoreService.java @@ -244,7 +244,8 @@ public interface IKeystoreService extends IInterface { return _result; } - public int generate(String name, int uid, int flags) throws RemoteException { + public int generate(String name, int uid, int keyType, int keySize, int flags, + byte[][] args) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); int _result; @@ -252,7 +253,17 @@ public interface IKeystoreService extends IInterface { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(name); _data.writeInt(uid); + _data.writeInt(keyType); + _data.writeInt(keySize); _data.writeInt(flags); + if (args == null) { + _data.writeInt(0); + } else { + _data.writeInt(args.length); + for (int i = 0; i < args.length; i++) { + _data.writeByteArray(args[i]); + } + } mRemote.transact(Stub.TRANSACTION_generate, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); @@ -560,7 +571,8 @@ public interface IKeystoreService extends IInterface { public int zero() throws RemoteException; - public int generate(String name, int uid, int flags) throws RemoteException; + public int generate(String name, int uid, int keyType, int keySize, int flags, byte[][] args) + throws RemoteException; public int import_key(String name, byte[] data, int uid, int flags) throws RemoteException; diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 51c5c7b..0bebc04 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -18,6 +18,8 @@ package android.view; import android.content.Context; import android.content.res.Resources; +import android.os.Build; +import android.os.Handler; import android.os.SystemClock; import android.util.FloatMath; @@ -128,6 +130,8 @@ public class ScaleGestureDetector { private float mFocusX; private float mFocusY; + private boolean mDoubleTapScales; + private float mCurrSpan; private float mPrevSpan; private float mInitialSpan; @@ -148,9 +152,14 @@ public class ScaleGestureDetector { private int mTouchHistoryDirection; private long mTouchHistoryLastAcceptedTime; private int mTouchMinMajor; + private MotionEvent mDoubleTapEvent; + private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE; + private final Handler mHandler; private static final long TOUCH_STABILIZE_TIME = 128; // ms - private static final int TOUCH_MIN_MAJOR = 48; // dp + private static final int DOUBLE_TAP_MODE_NONE = 0; + private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1; + /** * Consistency verifier for debugging purposes. @@ -158,8 +167,37 @@ public class ScaleGestureDetector { private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; + private GestureDetector mGestureDetector; + + private boolean mEventBeforeOrAboveStartingGestureEvent; + /** + * Creates a ScaleGestureDetector with the supplied listener. + * You may only use this constructor from a {@link android.os.Looper Looper} thread. + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * + * @throws NullPointerException if {@code listener} is null. + */ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) { + this(context, listener, null); + } + + /** + * Creates a ScaleGestureDetector with the supplied listener. + * @see android.os.Handler#Handler() + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use for running deferred listener events. + * + * @throws NullPointerException if {@code listener} is null. + */ + public ScaleGestureDetector(Context context, OnScaleGestureListener listener, + Handler handler) { mContext = context; mListener = listener; mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2; @@ -167,8 +205,12 @@ public class ScaleGestureDetector { final Resources res = context.getResources(); mTouchMinMajor = res.getDimensionPixelSize( com.android.internal.R.dimen.config_minScalingTouchMajor); - mMinSpan = res.getDimensionPixelSize( - com.android.internal.R.dimen.config_minScalingSpan); + mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan); + mHandler = handler; + // Quick scale is enabled by default after JB_MR2 + if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) { + setQuickScaleEnabled(true); + } } /** @@ -263,8 +305,14 @@ public class ScaleGestureDetector { final int action = event.getActionMasked(); + // Forward the event to check for double tap gesture + if (mDoubleTapScales) { + mGestureDetector.onTouchEvent(event); + } + final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL; + if (action == MotionEvent.ACTION_DOWN || streamComplete) { // Reset any scale in progress with the listener. // If it's an ACTION_DOWN we're beginning a new event stream. @@ -273,6 +321,7 @@ public class ScaleGestureDetector { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = 0; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (streamComplete) { @@ -284,21 +333,37 @@ public class ScaleGestureDetector { final boolean configChanged = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN; + + final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? event.getActionIndex() : -1; // Determine focal point float sumX = 0, sumY = 0; final int count = event.getPointerCount(); - for (int i = 0; i < count; i++) { - if (skipIndex == i) continue; - sumX += event.getX(i); - sumY += event.getY(i); - } final int div = pointerUp ? count - 1 : count; - final float focusX = sumX / div; - final float focusY = sumY / div; + final float focusX; + final float focusY; + if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) { + // In double tap mode, the focal pt is always where the double tap + // gesture started + focusX = mDoubleTapEvent.getX(); + focusY = mDoubleTapEvent.getY(); + if (event.getY() < focusY) { + mEventBeforeOrAboveStartingGestureEvent = true; + } else { + mEventBeforeOrAboveStartingGestureEvent = false; + } + } else { + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += event.getX(i); + sumY += event.getY(i); + } + focusX = sumX / div; + focusY = sumY / div; + } addTouchHistory(event); @@ -320,7 +385,12 @@ public class ScaleGestureDetector { // the focal point. final float spanX = devX * 2; final float spanY = devY * 2; - final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY); + final float span; + if (inDoubleTapMode()) { + span = spanY; + } else { + span = FloatMath.sqrt(spanX * spanX + spanY * spanY); + } // Dispatch begin/end events as needed. // If the configuration changes, notify the app to reset its current state by beginning @@ -328,10 +398,11 @@ public class ScaleGestureDetector { final boolean wasInProgress = mInProgress; mFocusX = focusX; mFocusY = focusY; - if (mInProgress && (span < mMinSpan || configChanged)) { + if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) { mListener.onScaleEnd(this); mInProgress = false; mInitialSpan = span; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (configChanged) { mPrevSpanX = mCurrSpanX = spanX; @@ -354,6 +425,7 @@ public class ScaleGestureDetector { mCurrSpan = span; boolean updatePrev = true; + if (mInProgress) { updatePrev = mListener.onScale(this); } @@ -369,6 +441,34 @@ public class ScaleGestureDetector { return true; } + + private boolean inDoubleTapMode() { + return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS; + } + + /** + * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks + * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default + * if the app targets API 19 and newer. + * @param scales true to enable quick scaling, false to disable + */ + public void setQuickScaleEnabled(boolean scales) { + mDoubleTapScales = scales; + if (mDoubleTapScales && mGestureDetector == null) { + GestureDetector.SimpleOnGestureListener gestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + // Double tap: start watching for a swipe + mDoubleTapEvent = e; + mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS; + return true; + } + }; + mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler); + } + } + /** * Returns {@code true} if a scale gesture is in progress. */ @@ -472,6 +572,12 @@ public class ScaleGestureDetector { * @return The current scaling factor. */ public float getScaleFactor() { + if (inDoubleTapMode() && mEventBeforeOrAboveStartingGestureEvent) { + // Drag is moving up; the further away from the gesture + // start, the smaller the span should be, the closer, + // the larger the span, and therefore the larger the scale + return (1 / mCurrSpan) / (1 / mPrevSpan); + } return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } @@ -493,4 +599,4 @@ public class ScaleGestureDetector { public long getEventTime() { return mCurrTime; } -} +}
\ No newline at end of file diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 6646579..ee47ac4 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -141,7 +141,7 @@ static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz, jboolean isShareable) { jobject brd = NULL; // for now we don't allow shareable with java inputstreams - SkStream* stream = CopyJavaInputStream(env, is, storage); + SkStreamRewindable* stream = CopyJavaInputStream(env, is, storage); if (stream) { brd = createBitmapRegionDecoder(env, stream); diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp index 797d155..d264392 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp @@ -159,8 +159,8 @@ private: friend class RewindableJavaStream; }; -SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, - jbyteArray storage) { +SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, + jbyteArray storage) { static bool gInited; if (!gInited) { @@ -190,6 +190,7 @@ SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, return new JavaInputStreamAdaptor(env, stream, storage); } + static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) { SkASSERT(adaptor != NULL); SkDynamicMemoryWStream wStream; @@ -203,9 +204,9 @@ static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) { return new SkMemoryStream(data.get()); } -SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream, - jbyteArray storage) { - SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage)); +SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage) { + SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); if (NULL == adaptor.get()) { return NULL; } @@ -302,7 +303,7 @@ static size_t get_length_if_supported(JNIEnv* env, jobject jstream) { SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream, jbyteArray storage) { - SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage)); + SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); if (NULL == adaptor.get()) { return NULL; } diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h index 5218dc5..fcc0c9a 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h @@ -24,8 +24,8 @@ class SkWStream; * function returns, since the Java InputStream is not managed * by the SkStream. */ -SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, - jbyteArray storage); +SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, + jbyteArray storage); /** * Copy a Java InputStream. @@ -33,13 +33,11 @@ SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, * @param stream Pointer to Java InputStream. * @param storage Java byte array for retrieving data from the * Java InputStream. - * @return SkMemoryStream The data in stream will be copied to a new - * SkMemoryStream. - * FIXME: Could return a more generic return type if ViewStateSerializer - * did not require an SkMemoryStream. + * @return SkStreamRewindable The data in stream will be copied + * to a new SkStreamRewindable. */ -SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream, - jbyteArray storage); +SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, + jbyteArray storage); /** * Get a rewindable stream from a Java InputStream. @@ -50,7 +48,7 @@ SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream, * @return SkStreamRewindable Either a wrapper around the Java * InputStream, if possible, or a copy which is rewindable. * Since it may be a wrapper, must not be used after the - * caller returns, like the result of WrapJavaInputStream. + * caller returns, like the result of CreateJavaInputStreamAdaptor. */ SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream, jbyteArray storage); @@ -69,5 +67,4 @@ android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject stream); SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage); - #endif diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index dff2b18..fcf22b8 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -39,9 +39,10 @@ public: static SkPicture* deserialize(JNIEnv* env, jobject, jobject jstream, jbyteArray jstorage) { SkPicture* picture = NULL; - SkAutoTUnref<SkStream> strm(WrapJavaInputStream(env, jstream, jstorage)); - if (strm.get()) { - picture = SkPicture::CreateFromStream(strm.get()); + SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage); + if (strm) { + picture = SkPicture::CreateFromStream(strm); + delete strm; } return picture; } diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index e2606d6..10cdab0 100644 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -63,8 +63,6 @@ public class DrmManagerClient { private final CloseGuard mCloseGuard = CloseGuard.get(); - private static final String EXTENDED_INFO_DATA = "extended_info_data"; - static { // Load the respective library System.loadLibrary("drmframework_jni"); @@ -186,22 +184,8 @@ public class DrmManagerClient { DrmManagerClient instance = (DrmManagerClient)((WeakReference)thisReference).get(); if (null != instance && null != instance.mInfoHandler) { - DrmInfoEvent event = new DrmInfoEvent(uniqueId, infoType, message); - Message m = instance.mInfoHandler.obtainMessage( - InfoHandler.INFO_EVENT_TYPE, event); - instance.mInfoHandler.sendMessage(m); - } - } - - private static void notify( - Object thisReference, int uniqueId, int infoType, String message, - HashMap<String, Object> attributes) { - DrmManagerClient instance = (DrmManagerClient)((WeakReference)thisReference).get(); - - if (null != instance && null != instance.mInfoHandler) { - DrmInfoEvent event = new DrmInfoEvent(uniqueId, infoType, message, attributes); Message m = instance.mInfoHandler.obtainMessage( - InfoHandler.INFO_EVENT_TYPE, event); + InfoHandler.INFO_EVENT_TYPE, uniqueId, infoType, message); instance.mInfoHandler.sendMessage(m); } } @@ -214,25 +198,23 @@ public class DrmManagerClient { } public void handleMessage(Message msg) { - DrmInfoEvent info = (DrmInfoEvent) msg.obj; + DrmInfoEvent info = null; DrmErrorEvent error = null; - int uniqueId; - int eventType; - String message; switch (msg.what) { case InfoHandler.INFO_EVENT_TYPE: - uniqueId = info.getUniqueId(); - eventType = info.getType(); - message = info.getMessage(); + int uniqueId = msg.arg1; + int infoType = msg.arg2; + String message = msg.obj.toString(); - switch (eventType) { + switch (infoType) { case DrmInfoEvent.TYPE_REMOVE_RIGHTS: { try { DrmUtils.removeFile(message); } catch (IOException e) { e.printStackTrace(); } + info = new DrmInfoEvent(uniqueId, infoType, message); break; } case DrmInfoEvent.TYPE_ALREADY_REGISTERED_BY_ANOTHER_ACCOUNT: @@ -240,11 +222,11 @@ public class DrmManagerClient { case DrmInfoEvent.TYPE_WAIT_FOR_RIGHTS: case DrmInfoEvent.TYPE_ACCOUNT_ALREADY_REGISTERED: case DrmInfoEvent.TYPE_RIGHTS_REMOVED: { + info = new DrmInfoEvent(uniqueId, infoType, message); break; } default: - info = null; - error = new DrmErrorEvent(uniqueId, eventType, message); + error = new DrmErrorEvent(uniqueId, infoType, message); break; } diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp index 7fce3d0..baddf62 100644 --- a/drm/jni/android_drm_DrmManagerClient.cpp +++ b/drm/jni/android_drm_DrmManagerClient.cpp @@ -169,49 +169,11 @@ void JNIOnInfoListener::onInfo(const DrmInfoEvent& event) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jstring message = env->NewStringUTF(event.getMessage().string()); ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().string()); - const DrmBuffer& drmBuffer = event.getData(); - if (event.getCount() > 0 || drmBuffer.length > 0) { - jclass hashMapClazz = env->FindClass("java/util/HashMap"); - jmethodID hashMapInitId = env->GetMethodID(hashMapClazz, "<init>", "()V"); - jmethodID hashMapPutId = env->GetMethodID(hashMapClazz, "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - jobject hashMapObject = env->NewObject(hashMapClazz, hashMapInitId); - env->DeleteLocalRef(hashMapClazz); - - if (0 < drmBuffer.length) { - jfieldID fid = env->GetStaticFieldID( - mClass, "EXTENDED_INFO_DATA", "Ljava/lang/String;"); - jstring key = (jstring) env->GetStaticObjectField(mClass, fid); - - jbyteArray valueByte = env->NewByteArray(drmBuffer.length); - env->SetByteArrayRegion(valueByte, 0, drmBuffer.length, (jbyte*) drmBuffer.data); - env->CallObjectMethod(hashMapObject, hashMapPutId, key, valueByte); - env->DeleteLocalRef(valueByte); - env->DeleteLocalRef(key); - } - DrmInfoEvent::KeyIterator keyIt = event.keyIterator(); - while (keyIt.hasNext()) { - String8 mapKey = keyIt.next(); - jstring key = env->NewStringUTF(mapKey.string()); - jstring value = env->NewStringUTF(event.get(mapKey).string()); - env->CallObjectMethod(hashMapObject, hashMapPutId, key, value); - env->DeleteLocalRef(value); - env->DeleteLocalRef(key); - } - env->CallStaticVoidMethod( - mClass, - env->GetStaticMethodID(mClass, "notify", - "(Ljava/lang/Object;IILjava/lang/String;Ljava/util/HashMap;)V"), - mObject, uniqueId, type, message, hashMapObject); - env->DeleteLocalRef(hashMapObject); - } else { - env->CallStaticVoidMethod( - mClass, - env->GetStaticMethodID(mClass, "notify", - "(Ljava/lang/Object;IILjava/lang/String;)V"), - mObject, uniqueId, type, message); - } - env->DeleteLocalRef(message); + + env->CallStaticVoidMethod( + mClass, + env->GetStaticMethodID(mClass, "notify", "(Ljava/lang/Object;IILjava/lang/String;)V"), + mObject, uniqueId, type, message); } static Mutex sLock; diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java index 390e732..1ab0aeb 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -18,6 +18,7 @@ package android.security; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; +import com.android.org.conscrypt.NativeCrypto; import com.android.org.conscrypt.OpenSSLEngine; import java.security.InvalidAlgorithmParameterException; @@ -33,7 +34,10 @@ import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAParameterSpec; +import java.security.spec.ECParameterSpec; import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.X509EncodedKeySpec; /** @@ -87,8 +91,12 @@ public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { Credentials.deleteAllTypesForAlias(mKeyStore, alias); + final int keyType = KeyStore.getKeyTypeForAlgorithm(mSpec.getKeyType()); + byte[][] args = getArgsForKeyType(keyType, mSpec.getAlgorithmParameterSpec()); + final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mSpec.getFlags())) { + if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, keyType, + mSpec.getKeySize(), mSpec.getFlags(), args)) { throw new IllegalStateException("could not generate key in keystore"); } @@ -104,10 +112,10 @@ public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { final PublicKey pubKey; try { - final KeyFactory keyFact = KeyFactory.getInstance("RSA"); + final KeyFactory keyFact = KeyFactory.getInstance(mSpec.getKeyType()); pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Can't instantiate RSA key generator", e); + throw new IllegalStateException("Can't instantiate key generator", e); } catch (InvalidKeySpecException e) { throw new IllegalStateException("keystore returned invalid key encoding", e); } @@ -119,7 +127,7 @@ public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { certGen.setIssuerDN(mSpec.getSubjectDN()); certGen.setNotBefore(mSpec.getStartDate()); certGen.setNotAfter(mSpec.getEndDate()); - certGen.setSignatureAlgorithm("sha1WithRSA"); + certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyType(mSpec.getKeyType())); final X509Certificate cert; try { @@ -146,6 +154,37 @@ public class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { return new KeyPair(pubKey, privKey); } + private static String getDefaultSignatureAlgorithmForKeyType(String keyType) { + if ("RSA".equalsIgnoreCase(keyType)) { + return "sha256WithRSA"; + } else if ("DSA".equalsIgnoreCase(keyType)) { + return "sha1WithDSA"; + } else if ("EC".equalsIgnoreCase(keyType)) { + return "sha256WithECDSA"; + } else { + throw new IllegalArgumentException("Unsupported key type " + keyType); + } + } + + private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) { + switch (keyType) { + case NativeCrypto.EVP_PKEY_RSA: + if (spec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; + return new byte[][] { rsaSpec.getPublicExponent().toByteArray() }; + } + break; + case NativeCrypto.EVP_PKEY_DSA: + if (spec instanceof DSAParameterSpec) { + DSAParameterSpec dsaSpec = (DSAParameterSpec) spec; + return new byte[][] { dsaSpec.getG().toByteArray(), + dsaSpec.getP().toByteArray(), dsaSpec.getQ().toByteArray() }; + } + break; + } + return null; + } + @Override public void initialize(int keysize, SecureRandom random) { throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator"); diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index 59f89bc..21d6caa 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -16,13 +16,18 @@ package android.security; +import com.android.org.conscrypt.NativeCrypto; + import android.content.Context; import android.text.TextUtils; import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; import java.util.Date; import javax.security.auth.x500.X500Principal; @@ -50,10 +55,35 @@ import javax.security.auth.x500.X500Principal; * certificate signed by a real Certificate Authority. */ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { - private final String mKeystoreAlias; + /* + * These must be kept in sync with system/security/keystore/defaults.h + */ + + /* DSA */ + private static final int DSA_DEFAULT_KEY_SIZE = 1024; + private static final int DSA_MIN_KEY_SIZE = 512; + private static final int DSA_MAX_KEY_SIZE = 8192; + + /* EC */ + private static final int EC_DEFAULT_KEY_SIZE = 256; + private static final int EC_MIN_KEY_SIZE = 192; + private static final int EC_MAX_KEY_SIZE = 521; + + /* RSA */ + private static final int RSA_DEFAULT_KEY_SIZE = 2048; + private static final int RSA_MIN_KEY_SIZE = 512; + private static final int RSA_MAX_KEY_SIZE = 8192; private final Context mContext; + private final String mKeystoreAlias; + + private final String mKeyType; + + private final int mKeySize; + + private final AlgorithmParameterSpec mSpec; + private final X500Principal mSubjectDN; private final BigInteger mSerialNumber; @@ -84,6 +114,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * @param context Android context for the activity * @param keyStoreAlias name to use for the generated key in the Android * keystore + * @param keyType key algorithm to use (RSA, DSA, EC) + * @param keySize size of key to generate + * @param spec the underlying key type parameters * @param subjectDN X.509 v3 Subject Distinguished Name * @param serialNumber X509 v3 certificate serial number * @param startDate the start of the self-signed certificate validity period @@ -93,9 +126,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * {@code endDate} is before {@code startDate}. * @hide should be built with KeyPairGeneratorSpecBuilder */ - public KeyPairGeneratorSpec(Context context, String keyStoreAlias, - X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate, - int flags) { + public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, + AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, + Date startDate, Date endDate, int flags) { if (context == null) { throw new IllegalArgumentException("context == null"); } else if (TextUtils.isEmpty(keyStoreAlias)) { @@ -112,8 +145,18 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { throw new IllegalArgumentException("endDate < startDate"); } + final int keyTypeInt = KeyStore.getKeyTypeForAlgorithm(keyType); + if (keySize == -1) { + keySize = getDefaultKeySizeForType(keyTypeInt); + } + checkCorrectParametersSpec(keyTypeInt, keySize, spec); + checkValidKeySize(keyTypeInt, keySize); + mContext = context; mKeystoreAlias = keyStoreAlias; + mKeyType = keyType; + mKeySize = keySize; + mSpec = spec; mSubjectDN = subjectDN; mSerialNumber = serialNumber; mStartDate = startDate; @@ -121,6 +164,64 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { mFlags = flags; } + private static int getDefaultKeySizeForType(int keyType) { + if (keyType == NativeCrypto.EVP_PKEY_DSA) { + return DSA_DEFAULT_KEY_SIZE; + } else if (keyType == NativeCrypto.EVP_PKEY_EC) { + return EC_DEFAULT_KEY_SIZE; + } else if (keyType == NativeCrypto.EVP_PKEY_RSA) { + return RSA_DEFAULT_KEY_SIZE; + } + throw new IllegalArgumentException("Invalid key type " + keyType); + } + + private static void checkValidKeySize(int keyType, int keySize) { + if (keyType == NativeCrypto.EVP_PKEY_DSA) { + if (keySize < DSA_MIN_KEY_SIZE || keySize > DSA_MAX_KEY_SIZE) { + throw new IllegalArgumentException("DSA keys must be >= " + DSA_MIN_KEY_SIZE + + " and <= " + DSA_MAX_KEY_SIZE); + } + } else if (keyType == NativeCrypto.EVP_PKEY_EC) { + if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) { + throw new IllegalArgumentException("EC keys must be >= " + EC_MIN_KEY_SIZE + + " and <= " + EC_MAX_KEY_SIZE); + } + } else if (keyType == NativeCrypto.EVP_PKEY_RSA) { + if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { + throw new IllegalArgumentException("RSA keys must be >= " + RSA_MIN_KEY_SIZE + + " and <= " + RSA_MAX_KEY_SIZE); + } + } else { + throw new IllegalArgumentException("Invalid key type " + keyType); + } + } + + private static void checkCorrectParametersSpec(int keyType, int keySize, + AlgorithmParameterSpec spec) { + if (keyType == NativeCrypto.EVP_PKEY_DSA && spec != null) { + if (!(spec instanceof DSAParameterSpec)) { + throw new IllegalArgumentException("DSA keys must have DSAParameterSpec specified"); + } + } else if (keyType == NativeCrypto.EVP_PKEY_RSA && spec != null) { + if (spec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec; + if (keySize != -1 && keySize != rsaSpec.getKeysize()) { + throw new IllegalArgumentException("RSA key size must match: " + keySize + + " vs " + rsaSpec.getKeysize()); + } + } else { + throw new IllegalArgumentException("RSA may only use RSAKeyGenParameterSpec"); + } + } + } + + /** + * Gets the Android context used for operations with this instance. + */ + public Context getContext() { + return mContext; + } + /** * Returns the alias that will be used in the {@code java.security.KeyStore} * in conjunction with the {@code AndroidKeyStore}. @@ -130,10 +231,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Gets the Android context used for operations with this instance. + * Returns the key type (e.g., "RSA", "DSA", "EC") specified by this + * parameter. */ - public Context getContext() { - return mContext; + public String getKeyType() { + return mKeyType; + } + + /** + * Returns the key size specified by this parameter. For instance, for RSA + * this will return the modulus size and for EC it will return the field + * size. + */ + public int getKeySize() { + return mKeySize; + } + + /** + * Returns the {@link AlgorithmParameterSpec} that will be used for creation + * of the key pair. + */ + public AlgorithmParameterSpec getAlgorithmParameterSpec() { + return mSpec; } /** @@ -209,6 +328,12 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private String mKeystoreAlias; + private String mKeyType = "RSA"; + + private int mKeySize = -1; + + private AlgorithmParameterSpec mSpec; + private X500Principal mSubjectDN; private BigInteger mSerialNumber; @@ -246,6 +371,49 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** + * Sets the key type (e.g., RSA, DSA, EC) of the keypair to be created. + */ + public Builder setKeyType(String keyType) throws NoSuchAlgorithmException { + if (keyType == null) { + throw new NullPointerException("keyType == null"); + } else { + try { + KeyStore.getKeyTypeForAlgorithm(keyType); + } catch (IllegalArgumentException e) { + throw new NoSuchAlgorithmException("Unsupported key type: " + keyType); + } + } + mKeyType = keyType; + return this; + } + + /** + * Sets the key size for the keypair to be created. For instance, for a + * key type of RSA this will set the modulus size and for a key type of + * EC it will select a curve with a matching field size. + */ + public Builder setKeySize(int keySize) { + if (keySize < 0) { + throw new IllegalArgumentException("keySize < 0"); + } + mKeySize = keySize; + return this; + } + + /** + * Sets the underlying key type's parameters. This is required for DSA + * where you must set this to an instance of + * {@link java.security.spec.DSAParameterSpec}. + */ + public Builder setAlgorithmParameterSpec(AlgorithmParameterSpec spec) { + if (spec == null) { + throw new NullPointerException("spec == null"); + } + mSpec = spec; + return this; + } + + /** * Sets the subject used for the self-signed certificate of the * generated key pair. */ @@ -311,8 +479,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * @return built instance of {@code KeyPairGeneratorSpec} */ public KeyPairGeneratorSpec build() { - return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mSubjectDN, - mSerialNumber, mStartDate, mEndDate, mFlags); + return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mKeyType, mKeySize, mSpec, + mSubjectDN, mSerialNumber, mStartDate, mEndDate, mFlags); } } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index fb5e039..9babb94 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -16,6 +16,8 @@ package android.security; +import com.android.org.conscrypt.NativeCrypto; + import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; @@ -64,6 +66,18 @@ public class KeyStore { return new KeyStore(keystore); } + static int getKeyTypeForAlgorithm(String keyType) throws IllegalArgumentException { + if ("RSA".equalsIgnoreCase(keyType)) { + return NativeCrypto.EVP_PKEY_RSA; + } else if ("DSA".equalsIgnoreCase(keyType)) { + return NativeCrypto.EVP_PKEY_DSA; + } else if ("EC".equalsIgnoreCase(keyType)) { + return NativeCrypto.EVP_PKEY_EC; + } else { + throw new IllegalArgumentException("Unsupported key type: " + keyType); + } + } + public State state() { final int ret; try { @@ -188,9 +202,10 @@ public class KeyStore { } } - public boolean generate(String key, int uid, int flags) { + public boolean generate(String key, int uid, int keyType, int keySize, int flags, + byte[][] args) { try { - return mBinder.generate(key, uid, flags) == NO_ERROR; + return mBinder.generate(key, uid, keyType, keySize, flags, args) == NO_ERROR; } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return false; diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java index 1582f74..ea6c43d 100644 --- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java +++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java @@ -27,6 +27,13 @@ import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; import java.text.SimpleDateFormat; import java.util.Date; @@ -118,6 +125,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { mGenerator.initialize( new KeyPairGeneratorSpec.Builder(getContext()) .setAlias(TEST_ALIAS_1) + .setKeyType("RSA") + .setKeySize(1024) .setSubject(TEST_DN_1) .setSerialNumber(TEST_SERIAL_1) .setStartDate(NOW) @@ -142,12 +151,14 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { final KeyPair pair = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair); - assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS); + assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); } - public void testKeyPairGenerator_GenerateKeyPair_Unencrypted_Success() throws Exception { + public void testKeyPairGenerator_GenerateKeyPair_DSA_Unencrypted_Success() throws Exception { mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) .setAlias(TEST_ALIAS_1) + .setKeyType("DSA") .setSubject(TEST_DN_1) .setSerialNumber(TEST_SERIAL_1) .setStartDate(NOW) @@ -157,7 +168,223 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { final KeyPair pair = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair); - assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS); + assertKeyPairCorrect(pair, TEST_ALIAS_1, "DSA", 1024, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_DSA_2048_Unencrypted_Success() + throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("DSA") + .setKeySize(2048) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "DSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_DSA_SpecifiedParams_Unencrypted_Success() + throws Exception { + /* + * generated using: openssl dsaparam -C 2048 + */ + BigInteger p = new BigInteger(1, new byte[] { + (byte) 0xC0, (byte) 0x3D, (byte) 0x86, (byte) 0x09, (byte) 0xCA, (byte) 0x8C, + (byte) 0x37, (byte) 0xCA, (byte) 0xCC, (byte) 0x4A, (byte) 0x81, (byte) 0xBD, + (byte) 0xD8, (byte) 0x50, (byte) 0x77, (byte) 0xCD, (byte) 0xDD, (byte) 0x32, + (byte) 0x0B, (byte) 0x43, (byte) 0xBF, (byte) 0x42, (byte) 0x06, (byte) 0x5A, + (byte) 0x3D, (byte) 0x18, (byte) 0x50, (byte) 0x47, (byte) 0x79, (byte) 0xE1, + (byte) 0x5B, (byte) 0x86, (byte) 0x03, (byte) 0xB9, (byte) 0x28, (byte) 0x9C, + (byte) 0x18, (byte) 0xA9, (byte) 0xF5, (byte) 0xD6, (byte) 0xF4, (byte) 0x94, + (byte) 0x5B, (byte) 0x87, (byte) 0x58, (byte) 0xCA, (byte) 0xB2, (byte) 0x1E, + (byte) 0xFC, (byte) 0xED, (byte) 0x37, (byte) 0xC3, (byte) 0x49, (byte) 0xAC, + (byte) 0xFA, (byte) 0x46, (byte) 0xDB, (byte) 0x7A, (byte) 0x50, (byte) 0x96, + (byte) 0xCF, (byte) 0x52, (byte) 0xD7, (byte) 0x4E, (byte) 0xEB, (byte) 0x26, + (byte) 0x41, (byte) 0xA2, (byte) 0x6F, (byte) 0x99, (byte) 0x80, (byte) 0x9F, + (byte) 0x0F, (byte) 0x0A, (byte) 0xA8, (byte) 0x0D, (byte) 0xAC, (byte) 0xAB, + (byte) 0xEF, (byte) 0x7D, (byte) 0xE7, (byte) 0x4C, (byte) 0xF1, (byte) 0x88, + (byte) 0x44, (byte) 0xC9, (byte) 0x17, (byte) 0xD0, (byte) 0xBB, (byte) 0xE2, + (byte) 0x01, (byte) 0x8C, (byte) 0xC1, (byte) 0x02, (byte) 0x1D, (byte) 0x3C, + (byte) 0x15, (byte) 0xB7, (byte) 0x41, (byte) 0x30, (byte) 0xD8, (byte) 0x11, + (byte) 0xBD, (byte) 0x6A, (byte) 0x2A, (byte) 0x0D, (byte) 0x36, (byte) 0x44, + (byte) 0x9C, (byte) 0x3F, (byte) 0x32, (byte) 0xE2, (byte) 0x1C, (byte) 0xFB, + (byte) 0xE3, (byte) 0xFF, (byte) 0xCC, (byte) 0x1A, (byte) 0x72, (byte) 0x38, + (byte) 0x37, (byte) 0x69, (byte) 0x5E, (byte) 0x35, (byte) 0x73, (byte) 0xE1, + (byte) 0x1E, (byte) 0x74, (byte) 0x35, (byte) 0x44, (byte) 0x07, (byte) 0xB5, + (byte) 0x2F, (byte) 0x0B, (byte) 0x60, (byte) 0xF4, (byte) 0xA9, (byte) 0xE0, + (byte) 0x81, (byte) 0xB2, (byte) 0xCD, (byte) 0x8B, (byte) 0x82, (byte) 0x76, + (byte) 0x7F, (byte) 0xD4, (byte) 0x17, (byte) 0x32, (byte) 0x86, (byte) 0x98, + (byte) 0x7C, (byte) 0x85, (byte) 0x66, (byte) 0xF6, (byte) 0x77, (byte) 0xED, + (byte) 0x8B, (byte) 0x1A, (byte) 0x52, (byte) 0x16, (byte) 0xDA, (byte) 0x1C, + (byte) 0xA7, (byte) 0x16, (byte) 0x79, (byte) 0x20, (byte) 0x1C, (byte) 0x99, + (byte) 0x5F, (byte) 0x12, (byte) 0x66, (byte) 0x15, (byte) 0x9F, (byte) 0xE5, + (byte) 0x73, (byte) 0xA9, (byte) 0x61, (byte) 0xBA, (byte) 0xA7, (byte) 0x23, + (byte) 0x93, (byte) 0x77, (byte) 0xB5, (byte) 0xF6, (byte) 0xEC, (byte) 0x13, + (byte) 0xBF, (byte) 0x95, (byte) 0x60, (byte) 0x78, (byte) 0x84, (byte) 0xE3, + (byte) 0x44, (byte) 0xEC, (byte) 0x74, (byte) 0xC2, (byte) 0xCB, (byte) 0xD4, + (byte) 0x70, (byte) 0xC5, (byte) 0x7B, (byte) 0xF8, (byte) 0x07, (byte) 0x3B, + (byte) 0xEB, (byte) 0x9F, (byte) 0xC9, (byte) 0x7D, (byte) 0xE0, (byte) 0xA5, + (byte) 0xBA, (byte) 0x68, (byte) 0x7B, (byte) 0xF4, (byte) 0x70, (byte) 0x40, + (byte) 0xAE, (byte) 0xE9, (byte) 0x65, (byte) 0xEE, (byte) 0x5B, (byte) 0x71, + (byte) 0x36, (byte) 0x0B, (byte) 0xB0, (byte) 0xA2, (byte) 0x98, (byte) 0x7D, + (byte) 0xE3, (byte) 0x24, (byte) 0x95, (byte) 0x2B, (byte) 0xC2, (byte) 0x0A, + (byte) 0x78, (byte) 0x3D, (byte) 0xCC, (byte) 0x3A, (byte) 0xEE, (byte) 0xED, + (byte) 0x48, (byte) 0xEB, (byte) 0xA3, (byte) 0x78, (byte) 0xA8, (byte) 0x9D, + (byte) 0x0A, (byte) 0x8F, (byte) 0x9E, (byte) 0x59, (byte) 0x2C, (byte) 0x44, + (byte) 0xB5, (byte) 0xF9, (byte) 0x53, (byte) 0x43, + }); + + BigInteger q = new BigInteger(1, new byte[] { + (byte) 0xA1, (byte) 0x9B, (byte) 0x1D, (byte) 0xC0, (byte) 0xE3, (byte) 0xF6, + (byte) 0x4A, (byte) 0x35, (byte) 0xE1, (byte) 0x8A, (byte) 0x43, (byte) 0xC2, + (byte) 0x9C, (byte) 0xF9, (byte) 0x52, (byte) 0x8F, (byte) 0x94, (byte) 0xA1, + (byte) 0x12, (byte) 0x11, (byte) 0xDB, (byte) 0x9A, (byte) 0xB6, (byte) 0x35, + (byte) 0x56, (byte) 0x26, (byte) 0x60, (byte) 0x89, (byte) 0x11, (byte) 0xAC, + (byte) 0xA8, (byte) 0xE5, + }); + + BigInteger g = new BigInteger(1, new byte[] { + (byte) 0xA1, (byte) 0x5C, (byte) 0x57, (byte) 0x15, (byte) 0xC3, (byte) 0xD9, + (byte) 0xD7, (byte) 0x41, (byte) 0x89, (byte) 0xD6, (byte) 0xB8, (byte) 0x7B, + (byte) 0xF3, (byte) 0xE0, (byte) 0xB3, (byte) 0xC5, (byte) 0xD1, (byte) 0xAA, + (byte) 0xF9, (byte) 0x55, (byte) 0x48, (byte) 0xF1, (byte) 0xDA, (byte) 0xE8, + (byte) 0x6F, (byte) 0x51, (byte) 0x05, (byte) 0xB2, (byte) 0xC9, (byte) 0x64, + (byte) 0xDA, (byte) 0x5F, (byte) 0xD4, (byte) 0xAA, (byte) 0xFD, (byte) 0x67, + (byte) 0xE0, (byte) 0x10, (byte) 0x2C, (byte) 0x1F, (byte) 0x03, (byte) 0x10, + (byte) 0xD4, (byte) 0x4B, (byte) 0x20, (byte) 0x82, (byte) 0x2B, (byte) 0x04, + (byte) 0xF9, (byte) 0x09, (byte) 0xAE, (byte) 0x28, (byte) 0x3D, (byte) 0x9B, + (byte) 0xFF, (byte) 0x87, (byte) 0x76, (byte) 0xCD, (byte) 0xF0, (byte) 0x11, + (byte) 0xB7, (byte) 0xEA, (byte) 0xE6, (byte) 0xCD, (byte) 0x60, (byte) 0xD3, + (byte) 0x8C, (byte) 0x74, (byte) 0xD3, (byte) 0x45, (byte) 0x63, (byte) 0x69, + (byte) 0x3F, (byte) 0x1D, (byte) 0x31, (byte) 0x25, (byte) 0x49, (byte) 0x97, + (byte) 0x4B, (byte) 0x73, (byte) 0x34, (byte) 0x12, (byte) 0x73, (byte) 0x27, + (byte) 0x4C, (byte) 0xDA, (byte) 0xF3, (byte) 0x08, (byte) 0xA8, (byte) 0xA9, + (byte) 0x27, (byte) 0xE4, (byte) 0xB8, (byte) 0xD6, (byte) 0xB5, (byte) 0xC4, + (byte) 0x18, (byte) 0xED, (byte) 0xBD, (byte) 0x6F, (byte) 0xA2, (byte) 0x36, + (byte) 0xA2, (byte) 0x9C, (byte) 0x27, (byte) 0x62, (byte) 0x7F, (byte) 0x93, + (byte) 0xD7, (byte) 0x52, (byte) 0xA9, (byte) 0x76, (byte) 0x55, (byte) 0x99, + (byte) 0x00, (byte) 0x5B, (byte) 0xC2, (byte) 0xB9, (byte) 0x18, (byte) 0xAC, + (byte) 0x6B, (byte) 0x83, (byte) 0x0D, (byte) 0xA1, (byte) 0xC5, (byte) 0x01, + (byte) 0x1A, (byte) 0xE5, (byte) 0x4D, (byte) 0x2F, (byte) 0xCF, (byte) 0x5D, + (byte) 0xB2, (byte) 0xE7, (byte) 0xC7, (byte) 0xCB, (byte) 0x2C, (byte) 0xFF, + (byte) 0x51, (byte) 0x1B, (byte) 0x9D, (byte) 0xA4, (byte) 0x05, (byte) 0xEB, + (byte) 0x17, (byte) 0xD8, (byte) 0x97, (byte) 0x9D, (byte) 0x0C, (byte) 0x59, + (byte) 0x92, (byte) 0x8A, (byte) 0x03, (byte) 0x34, (byte) 0xFD, (byte) 0x16, + (byte) 0x0F, (byte) 0x2A, (byte) 0xF9, (byte) 0x7D, (byte) 0xC3, (byte) 0x41, + (byte) 0x0D, (byte) 0x06, (byte) 0x5A, (byte) 0x4B, (byte) 0x34, (byte) 0xD5, + (byte) 0xF5, (byte) 0x09, (byte) 0x1C, (byte) 0xCE, (byte) 0xA7, (byte) 0x19, + (byte) 0x6D, (byte) 0x04, (byte) 0x53, (byte) 0x71, (byte) 0xCC, (byte) 0x84, + (byte) 0xA0, (byte) 0xB2, (byte) 0xA0, (byte) 0x68, (byte) 0xA3, (byte) 0x40, + (byte) 0xC0, (byte) 0x67, (byte) 0x38, (byte) 0x96, (byte) 0x73, (byte) 0x2E, + (byte) 0x8E, (byte) 0x2A, (byte) 0x9D, (byte) 0x56, (byte) 0xE9, (byte) 0xAC, + (byte) 0xC7, (byte) 0xEC, (byte) 0x84, (byte) 0x7F, (byte) 0xFC, (byte) 0xE0, + (byte) 0x69, (byte) 0x03, (byte) 0x8B, (byte) 0x48, (byte) 0x64, (byte) 0x76, + (byte) 0x85, (byte) 0xA5, (byte) 0x10, (byte) 0xD9, (byte) 0x31, (byte) 0xC3, + (byte) 0x8B, (byte) 0x07, (byte) 0x48, (byte) 0x62, (byte) 0xF6, (byte) 0x68, + (byte) 0xF2, (byte) 0x96, (byte) 0xB2, (byte) 0x18, (byte) 0x5B, (byte) 0xFF, + (byte) 0x6D, (byte) 0xD1, (byte) 0x6B, (byte) 0xF5, (byte) 0xFD, (byte) 0x81, + (byte) 0xF1, (byte) 0xFD, (byte) 0x04, (byte) 0xF0, (byte) 0x9F, (byte) 0xB7, + (byte) 0x08, (byte) 0x95, (byte) 0x57, (byte) 0x48, (byte) 0x07, (byte) 0x00, + (byte) 0x52, (byte) 0xEC, (byte) 0x75, (byte) 0x91, (byte) 0x02, (byte) 0x11, + (byte) 0xA3, (byte) 0x64, (byte) 0x26, (byte) 0xCA, + }); + + AlgorithmParameterSpec spec = new DSAParameterSpec(p, q, g); + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("DSA") + .setKeySize(2048) + .setAlgorithmParameterSpec(spec) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "DSA", 2048, spec, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("EC") + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeyType("EC") + .setKeySize(521) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception { + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); + } + + public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success() + throws Exception { + AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L)); + mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext()) + .setAlias(TEST_ALIAS_1) + .setKeySize(1024) + .setAlgorithmParameterSpec(spec) + .setSubject(TEST_DN_1) + .setSerialNumber(TEST_SERIAL_1) + .setStartDate(NOW) + .setEndDate(NOW_PLUS_10_YEARS) + .build()); + + final KeyPair pair = mGenerator.generateKeyPair(); + assertNotNull("The KeyPair returned should not be null", pair); + + assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, TEST_DN_1, TEST_SERIAL_1, NOW, + NOW_PLUS_10_YEARS); } public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception { @@ -172,8 +399,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { .build()); final KeyPair pair1 = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair1); - assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, - NOW_PLUS_10_YEARS); + assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, + NOW, NOW_PLUS_10_YEARS); } // Replace the original key @@ -187,8 +414,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { .build()); final KeyPair pair2 = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair2); - assertKeyPairCorrect(pair2, TEST_ALIAS_2, TEST_DN_2, TEST_SERIAL_2, NOW, - NOW_PLUS_10_YEARS); + assertKeyPairCorrect(pair2, TEST_ALIAS_2, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2, + NOW, NOW_PLUS_10_YEARS); } } @@ -205,8 +432,8 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { .build()); final KeyPair pair1 = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair1); - assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, - NOW_PLUS_10_YEARS); + assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, + NOW, NOW_PLUS_10_YEARS); } // Attempt to replace previous key @@ -230,18 +457,45 @@ public class AndroidKeyPairGeneratorTest extends AndroidTestCase { final KeyPair pair2 = mGenerator.generateKeyPair(); assertNotNull("The KeyPair returned should not be null", pair2); - assertKeyPairCorrect(pair2, TEST_ALIAS_1, TEST_DN_2, TEST_SERIAL_2, NOW, - NOW_PLUS_10_YEARS); + assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2, + NOW, NOW_PLUS_10_YEARS); } } - private void assertKeyPairCorrect(KeyPair pair, String alias, X500Principal dn, - BigInteger serial, Date start, Date end) throws Exception { + private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize, + AlgorithmParameterSpec spec, X500Principal dn, BigInteger serial, Date start, Date end) + throws Exception { final PublicKey pubKey = pair.getPublic(); assertNotNull("The PublicKey for the KeyPair should be not null", pubKey); + assertEquals(keyType, pubKey.getAlgorithm()); + + if ("DSA".equalsIgnoreCase(keyType)) { + DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey; + DSAParams actualParams = dsaPubKey.getParams(); + assertEquals(keySize, (actualParams.getP().bitLength() + 7) & ~7); + if (spec != null) { + DSAParameterSpec expectedParams = (DSAParameterSpec) spec; + assertEquals(expectedParams.getP(), actualParams.getP()); + assertEquals(expectedParams.getQ(), actualParams.getQ()); + assertEquals(expectedParams.getG(), actualParams.getG()); + } + } else if ("EC".equalsIgnoreCase(keyType)) { + assertEquals("Curve should be what was specified during initialization", keySize, + ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize()); + } else if ("RSA".equalsIgnoreCase(keyType)) { + RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey; + assertEquals("Modulus size should be what is specified during initialization", + (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7); + if (spec != null) { + RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec; + assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7); + assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent()); + } + } final PrivateKey privKey = pair.getPrivate(); assertNotNull("The PrivateKey for the KeyPair should be not null", privKey); + assertEquals(keyType, privKey.getAlgorithm()); final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias); assertNotNull("The user certificate should exist for the generated entry", userCertBytes); diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/AndroidKeyStoreTest.java index b7129db..6597d3f 100644 --- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java +++ b/keystore/tests/src/android/security/AndroidKeyStoreTest.java @@ -18,7 +18,9 @@ package android.security; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; +import com.android.org.conscrypt.NativeCrypto; import com.android.org.conscrypt.OpenSSLEngine; +import com.android.org.conscrypt.OpenSSLKeyHolder; import android.test.AndroidTestCase; @@ -39,6 +41,10 @@ import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; @@ -99,7 +105,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { * * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' */ - private static final byte[] FAKE_CA_1 = { + private static final byte[] FAKE_RSA_CA_1 = { (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a, @@ -228,7 +234,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { * * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' */ - private static final byte[] FAKE_KEY_1 = new byte[] { + private static final byte[] FAKE_RSA_KEY_1 = new byte[] { (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, @@ -342,7 +348,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { * * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' */ - private static final byte[] FAKE_USER_1 = new byte[] { + private static final byte[] FAKE_RSA_USER_1 = new byte[] { (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, @@ -456,6 +462,628 @@ public class AndroidKeyStoreTest extends AndroidTestCase { (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72 }; + /* + * The keys and certificates below are generated with: + * + * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem + * openssl ecparam -name prime256v1 -out ecparam.pem + * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req + * mkdir -p demoCA/newcerts + * touch demoCA/index.txt + * echo "01" > demoCA/serial + * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650 + */ + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_EC_CA_1 = { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82, + (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2, + (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, + (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, + (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, + (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, + (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, + (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, + (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, + (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, + (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, + (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, + (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, + (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, + (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38, + (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, + (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32, + (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45, + (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, + (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, + (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, + (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, + (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, + (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, + (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, + (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, + (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d, + (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81, + (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f, + (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d, + (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7, + (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f, + (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36, + (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56, + (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09, + (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc, + (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03, + (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98, + (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae, + (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f, + (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b, + (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58, + (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9, + (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95, + (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8, + (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74, + (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f, + (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31, + (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47, + (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03, + (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30, + (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, + (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, + (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, + (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, + (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30, + (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, + (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, + (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72, + (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30, + (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, + (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, + (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, + (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, + (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc, + (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34, + (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2, + (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e, + (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4, + (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5, + (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19, + (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3, + (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01, + (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33, + (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06, + (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18, + (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e, + (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4, + (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d, + (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24, + (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36, + (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1, + (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c, + (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1, + (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f, + (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee + }; + + /** + * Generated from above and converted with: + * + * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_EC_KEY_1 = new byte[] { + (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00, + (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, + (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, + (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30, + (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20, + (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70, + (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5, + (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e, + (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4, + (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7, + (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42, + (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b, + (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14, + (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd, + (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3, + (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79, + (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1, + (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50, + (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6, + (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b, + (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e, + (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38 + }; + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_EC_USER_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82, + (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, + (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, + (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, + (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, + (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, + (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, + (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, + (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, + (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, + (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, + (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, + (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, + (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33, + (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36, + (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17, + (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, + (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30, + (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b, + (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, + (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, + (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, + (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, + (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, + (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, + (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, + (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, + (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12, + (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72, + (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, + (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, + (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, + (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, + (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, + (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03, + (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, + (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, + (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, + (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, + (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, + (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, + (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, + (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, + (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, + (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, + (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, + (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, + (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c, + (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, + (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04, + (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65, + (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47, + (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74, + (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72, + (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61, + (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, + (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2, + (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb, + (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d, + (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, + (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, + (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, + (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, + (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, + (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08, + (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05, + (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20, + (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1, + (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e, + (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d, + (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3, + (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d, + (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad, + (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb, + (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde, + (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18, + (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29, + (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5, + (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29, + (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25, + (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13, + (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51, + (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98, + (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38, + (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89, + (byte) 0x7e, (byte) 0xde, (byte) 0xb2 + }; + + /* + * The keys and certificates below are generated with: + * + * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem + * openssl dsaparam -out dsaparam.pem 1024 + * openssl req -newkey dsa:dsaparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req + * mkdir -p demoCA/newcerts + * touch demoCA/index.txt + * echo "01" > demoCA/serial + * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650 + */ + + /** + * Generated from above and converted with: + * + * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_DSA_CA_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x8a, (byte) 0x30, (byte) 0x82, + (byte) 0x01, (byte) 0xf3, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0x87, (byte) 0xc0, + (byte) 0x68, (byte) 0x7f, (byte) 0x42, (byte) 0x92, (byte) 0x0b, (byte) 0x7a, + (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, + (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, + (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x5e, (byte) 0x31, + (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, + (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, + (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, + (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, + (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, + (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, + (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, + (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, + (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, + (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17, (byte) 0x30, (byte) 0x15, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, + (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e, (byte) 0x65, (byte) 0x78, + (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, + (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x1e, (byte) 0x17, + (byte) 0x0d, (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, + (byte) 0x37, (byte) 0x32, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x32, + (byte) 0x39, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, + (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x32, (byte) 0x33, + (byte) 0x33, (byte) 0x31, (byte) 0x32, (byte) 0x39, (byte) 0x5a, (byte) 0x30, + (byte) 0x5e, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, + (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, + (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, + (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, + (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, + (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, + (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, + (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, + (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17, + (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x03, (byte) 0x0c, (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e, + (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, + (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, + (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, + (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, + (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, + (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, + (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa4, (byte) 0xc7, + (byte) 0x06, (byte) 0xba, (byte) 0xdf, (byte) 0x2b, (byte) 0xee, (byte) 0xd2, + (byte) 0xb9, (byte) 0xe4, (byte) 0x52, (byte) 0x21, (byte) 0x68, (byte) 0x2b, + (byte) 0x83, (byte) 0xdf, (byte) 0xe3, (byte) 0x9c, (byte) 0x08, (byte) 0x73, + (byte) 0xdd, (byte) 0x90, (byte) 0xea, (byte) 0x97, (byte) 0x0c, (byte) 0x96, + (byte) 0x20, (byte) 0xb1, (byte) 0xee, (byte) 0x11, (byte) 0xd5, (byte) 0xd4, + (byte) 0x7c, (byte) 0x44, (byte) 0x96, (byte) 0x2e, (byte) 0x6e, (byte) 0xa2, + (byte) 0xb2, (byte) 0xa3, (byte) 0x4b, (byte) 0x0f, (byte) 0x32, (byte) 0x90, + (byte) 0xaf, (byte) 0x5c, (byte) 0x6f, (byte) 0x00, (byte) 0x88, (byte) 0x45, + (byte) 0x4e, (byte) 0x9b, (byte) 0x26, (byte) 0xc1, (byte) 0x94, (byte) 0x3c, + (byte) 0xfe, (byte) 0x10, (byte) 0xbd, (byte) 0xda, (byte) 0xf2, (byte) 0x8d, + (byte) 0x03, (byte) 0x52, (byte) 0x32, (byte) 0x11, (byte) 0xff, (byte) 0xf6, + (byte) 0xf9, (byte) 0x6e, (byte) 0x8f, (byte) 0x0f, (byte) 0xc8, (byte) 0x0a, + (byte) 0x48, (byte) 0x39, (byte) 0x33, (byte) 0xb9, (byte) 0x0c, (byte) 0xb3, + (byte) 0x2b, (byte) 0xab, (byte) 0x7d, (byte) 0x79, (byte) 0x6f, (byte) 0x57, + (byte) 0x5b, (byte) 0xb8, (byte) 0x84, (byte) 0xb6, (byte) 0xcc, (byte) 0xe8, + (byte) 0x30, (byte) 0x78, (byte) 0xff, (byte) 0x92, (byte) 0xe5, (byte) 0x43, + (byte) 0x2e, (byte) 0xef, (byte) 0x66, (byte) 0x98, (byte) 0xb4, (byte) 0xfe, + (byte) 0xa2, (byte) 0x40, (byte) 0xf2, (byte) 0x1f, (byte) 0xd0, (byte) 0x86, + (byte) 0x16, (byte) 0xc8, (byte) 0x45, (byte) 0xc4, (byte) 0x52, (byte) 0xcb, + (byte) 0x31, (byte) 0x5c, (byte) 0x9f, (byte) 0x32, (byte) 0x3b, (byte) 0xf7, + (byte) 0x19, (byte) 0x08, (byte) 0xc7, (byte) 0x00, (byte) 0x21, (byte) 0x7d, + (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, + (byte) 0x50, (byte) 0x30, (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, + (byte) 0x04, (byte) 0x14, (byte) 0x47, (byte) 0x82, (byte) 0xa3, (byte) 0xf1, + (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, (byte) 0xde, (byte) 0x4f, (byte) 0x30, + (byte) 0x4c, (byte) 0x7f, (byte) 0x72, (byte) 0x81, (byte) 0x15, (byte) 0x32, + (byte) 0xda, (byte) 0x7f, (byte) 0x58, (byte) 0x18, (byte) 0x30, (byte) 0x1f, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, + (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x47, + (byte) 0x82, (byte) 0xa3, (byte) 0xf1, (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, + (byte) 0xde, (byte) 0x4f, (byte) 0x30, (byte) 0x4c, (byte) 0x7f, (byte) 0x72, + (byte) 0x81, (byte) 0x15, (byte) 0x32, (byte) 0xda, (byte) 0x7f, (byte) 0x58, + (byte) 0x18, (byte) 0x30, (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, + (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, + (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, + (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, + (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x08, (byte) 0x7f, + (byte) 0x6a, (byte) 0x48, (byte) 0x90, (byte) 0x7b, (byte) 0x9b, (byte) 0x72, + (byte) 0x13, (byte) 0xa7, (byte) 0xef, (byte) 0x6b, (byte) 0x0b, (byte) 0x59, + (byte) 0xe5, (byte) 0x49, (byte) 0x72, (byte) 0x3a, (byte) 0xc8, (byte) 0x84, + (byte) 0xcc, (byte) 0x23, (byte) 0x18, (byte) 0x4c, (byte) 0xec, (byte) 0xc7, + (byte) 0xef, (byte) 0xcb, (byte) 0xa7, (byte) 0xbe, (byte) 0xe4, (byte) 0xef, + (byte) 0x8f, (byte) 0xc6, (byte) 0x06, (byte) 0x8c, (byte) 0xc0, (byte) 0xe4, + (byte) 0x2f, (byte) 0x2a, (byte) 0xc0, (byte) 0x35, (byte) 0x7d, (byte) 0x5e, + (byte) 0x19, (byte) 0x29, (byte) 0x8c, (byte) 0xb9, (byte) 0xf1, (byte) 0x1e, + (byte) 0xaf, (byte) 0x82, (byte) 0xd8, (byte) 0xe3, (byte) 0x88, (byte) 0xe1, + (byte) 0x31, (byte) 0xc8, (byte) 0x82, (byte) 0x1f, (byte) 0x83, (byte) 0xa9, + (byte) 0xde, (byte) 0xfe, (byte) 0x4b, (byte) 0xe2, (byte) 0x78, (byte) 0x64, + (byte) 0xed, (byte) 0xa4, (byte) 0x7b, (byte) 0xee, (byte) 0x8d, (byte) 0x71, + (byte) 0x1b, (byte) 0x44, (byte) 0xe6, (byte) 0xb7, (byte) 0xe8, (byte) 0xc5, + (byte) 0x9a, (byte) 0x93, (byte) 0x92, (byte) 0x6f, (byte) 0x6f, (byte) 0xdb, + (byte) 0xbd, (byte) 0xd7, (byte) 0x03, (byte) 0x85, (byte) 0xa9, (byte) 0x5f, + (byte) 0x53, (byte) 0x5f, (byte) 0x5d, (byte) 0x30, (byte) 0xc6, (byte) 0xd9, + (byte) 0xce, (byte) 0x34, (byte) 0xa8, (byte) 0xbe, (byte) 0x31, (byte) 0x47, + (byte) 0x1c, (byte) 0xa4, (byte) 0x7f, (byte) 0xc0, (byte) 0x2c, (byte) 0xbc, + (byte) 0xfe, (byte) 0x1a, (byte) 0x31, (byte) 0xd8, (byte) 0x77, (byte) 0x4d, + (byte) 0xfc, (byte) 0x45, (byte) 0x84, (byte) 0xfc, (byte) 0x45, (byte) 0x12, + (byte) 0xab, (byte) 0x50, (byte) 0xe4, (byte) 0x45, (byte) 0xe5, (byte) 0x11 + }; + + /** + * Generated from above and converted with: openssl pkcs8 -topk8 -outform d + * -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_DSA_KEY_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x4c, (byte) 0x02, (byte) 0x01, + (byte) 0x00, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x2c, (byte) 0x06, + (byte) 0x07, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x38, + (byte) 0x04, (byte) 0x01, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x1f, + (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xb3, (byte) 0x23, + (byte) 0xf7, (byte) 0x86, (byte) 0xbd, (byte) 0x3b, (byte) 0x86, (byte) 0xcc, + (byte) 0xc3, (byte) 0x91, (byte) 0xc0, (byte) 0x30, (byte) 0x32, (byte) 0x02, + (byte) 0x47, (byte) 0x35, (byte) 0x01, (byte) 0xef, (byte) 0xee, (byte) 0x98, + (byte) 0x13, (byte) 0x56, (byte) 0x49, (byte) 0x47, (byte) 0xb5, (byte) 0x20, + (byte) 0xa8, (byte) 0x60, (byte) 0xcb, (byte) 0xc0, (byte) 0xd5, (byte) 0x77, + (byte) 0xc1, (byte) 0x69, (byte) 0xcd, (byte) 0x18, (byte) 0x34, (byte) 0x92, + (byte) 0xf2, (byte) 0x6a, (byte) 0x2a, (byte) 0x10, (byte) 0x59, (byte) 0x1c, + (byte) 0x91, (byte) 0x20, (byte) 0x51, (byte) 0xca, (byte) 0x37, (byte) 0xb2, + (byte) 0x87, (byte) 0xa6, (byte) 0x8a, (byte) 0x02, (byte) 0xfd, (byte) 0x45, + (byte) 0x46, (byte) 0xf9, (byte) 0x76, (byte) 0xb1, (byte) 0x35, (byte) 0x38, + (byte) 0x8d, (byte) 0xff, (byte) 0x4c, (byte) 0x5d, (byte) 0x75, (byte) 0x8f, + (byte) 0x66, (byte) 0x15, (byte) 0x7d, (byte) 0x7b, (byte) 0xda, (byte) 0xdb, + (byte) 0x57, (byte) 0x39, (byte) 0xff, (byte) 0x91, (byte) 0x3f, (byte) 0xdd, + (byte) 0xe2, (byte) 0xb4, (byte) 0x22, (byte) 0x60, (byte) 0x4c, (byte) 0x32, + (byte) 0x3b, (byte) 0x9d, (byte) 0x34, (byte) 0x9f, (byte) 0xb9, (byte) 0x5d, + (byte) 0x75, (byte) 0xb9, (byte) 0xd3, (byte) 0x7f, (byte) 0x11, (byte) 0xba, + (byte) 0xb7, (byte) 0xc8, (byte) 0x32, (byte) 0xc6, (byte) 0xce, (byte) 0x71, + (byte) 0x91, (byte) 0xd3, (byte) 0x32, (byte) 0xaf, (byte) 0x4d, (byte) 0x7e, + (byte) 0x7c, (byte) 0x15, (byte) 0xf7, (byte) 0x71, (byte) 0x2c, (byte) 0x52, + (byte) 0x65, (byte) 0x4d, (byte) 0xa9, (byte) 0x81, (byte) 0x25, (byte) 0x35, + (byte) 0xce, (byte) 0x0b, (byte) 0x5b, (byte) 0x56, (byte) 0xfe, (byte) 0xf1, + (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xeb, (byte) 0x4e, (byte) 0x7f, + (byte) 0x7a, (byte) 0x31, (byte) 0xb3, (byte) 0x7d, (byte) 0x8d, (byte) 0xb2, + (byte) 0xf7, (byte) 0xaf, (byte) 0xad, (byte) 0xb1, (byte) 0x42, (byte) 0x92, + (byte) 0xf3, (byte) 0x6c, (byte) 0xe4, (byte) 0xed, (byte) 0x8b, (byte) 0x02, + (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x81, (byte) 0xc8, (byte) 0x36, + (byte) 0x48, (byte) 0xdb, (byte) 0x71, (byte) 0x2b, (byte) 0x91, (byte) 0xce, + (byte) 0x6d, (byte) 0xbc, (byte) 0xb8, (byte) 0xf9, (byte) 0xcb, (byte) 0x50, + (byte) 0x91, (byte) 0x10, (byte) 0x8a, (byte) 0xf8, (byte) 0x37, (byte) 0x50, + (byte) 0xda, (byte) 0x4f, (byte) 0xc8, (byte) 0x4d, (byte) 0x73, (byte) 0xcb, + (byte) 0x4d, (byte) 0xb0, (byte) 0x19, (byte) 0x54, (byte) 0x5a, (byte) 0xf3, + (byte) 0x6c, (byte) 0xc9, (byte) 0xd8, (byte) 0x96, (byte) 0xd9, (byte) 0xb0, + (byte) 0x54, (byte) 0x7e, (byte) 0x7d, (byte) 0xe2, (byte) 0x58, (byte) 0x0e, + (byte) 0x5f, (byte) 0xc0, (byte) 0xce, (byte) 0xb9, (byte) 0x5c, (byte) 0xe3, + (byte) 0xd3, (byte) 0xdf, (byte) 0xcf, (byte) 0x45, (byte) 0x74, (byte) 0xfb, + (byte) 0xe6, (byte) 0x20, (byte) 0xe7, (byte) 0xfc, (byte) 0x0f, (byte) 0xca, + (byte) 0xdb, (byte) 0xc0, (byte) 0x0b, (byte) 0xe1, (byte) 0x5a, (byte) 0x16, + (byte) 0x1d, (byte) 0xb3, (byte) 0x2e, (byte) 0xe5, (byte) 0x5f, (byte) 0x89, + (byte) 0x17, (byte) 0x73, (byte) 0x50, (byte) 0xd1, (byte) 0x4a, (byte) 0x60, + (byte) 0xb7, (byte) 0xaa, (byte) 0xf0, (byte) 0xc7, (byte) 0xc5, (byte) 0x03, + (byte) 0x4e, (byte) 0x36, (byte) 0x51, (byte) 0x9e, (byte) 0x2f, (byte) 0xfa, + (byte) 0xf3, (byte) 0xd6, (byte) 0x58, (byte) 0x14, (byte) 0x02, (byte) 0xb4, + (byte) 0x41, (byte) 0xd6, (byte) 0x72, (byte) 0x6f, (byte) 0x58, (byte) 0x5b, + (byte) 0x2d, (byte) 0x23, (byte) 0xc0, (byte) 0x75, (byte) 0x4f, (byte) 0x39, + (byte) 0xa8, (byte) 0x6a, (byte) 0xdf, (byte) 0x79, (byte) 0x21, (byte) 0xf2, + (byte) 0x77, (byte) 0x91, (byte) 0x3f, (byte) 0x1c, (byte) 0x4d, (byte) 0x48, + (byte) 0x78, (byte) 0xcd, (byte) 0xed, (byte) 0x79, (byte) 0x23, (byte) 0x04, + (byte) 0x17, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xc7, (byte) 0xe7, + (byte) 0xe2, (byte) 0x6b, (byte) 0x14, (byte) 0xe6, (byte) 0x31, (byte) 0x12, + (byte) 0xb2, (byte) 0x1e, (byte) 0xd4, (byte) 0xf2, (byte) 0x9b, (byte) 0x2c, + (byte) 0xf6, (byte) 0x54, (byte) 0x4c, (byte) 0x12, (byte) 0xe8, (byte) 0x22 + }; + + /** + * Generated from above and converted with: openssl x509 -outform d -in + * usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] FAKE_DSA_USER_1 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x03, (byte) 0xca, (byte) 0x30, (byte) 0x82, + (byte) 0x03, (byte) 0x33, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01, + (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d, + (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, + (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, + (byte) 0x00, (byte) 0x30, (byte) 0x5e, (byte) 0x31, (byte) 0x0b, (byte) 0x30, + (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, + (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, + (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, + (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, + (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, + (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, + (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, + (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, + (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, + (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, + (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, + (byte) 0x31, (byte) 0x17, (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x0e, (byte) 0x63, + (byte) 0x61, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, + (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, + (byte) 0x6d, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, + (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x32, + (byte) 0x33, (byte) 0x33, (byte) 0x34, (byte) 0x32, (byte) 0x32, (byte) 0x5a, + (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, + (byte) 0x32, (byte) 0x35, (byte) 0x32, (byte) 0x33, (byte) 0x33, (byte) 0x34, + (byte) 0x32, (byte) 0x32, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, + (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, + (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, + (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, + (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, + (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, + (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, + (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, + (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, + (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, + (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, + (byte) 0x12, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, + (byte) 0x72, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, + (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, + (byte) 0x6d, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0xb7, (byte) 0x30, + (byte) 0x82, (byte) 0x01, (byte) 0x2c, (byte) 0x06, (byte) 0x07, (byte) 0x2a, + (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x38, (byte) 0x04, (byte) 0x01, + (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x1f, (byte) 0x02, (byte) 0x81, + (byte) 0x81, (byte) 0x00, (byte) 0xb3, (byte) 0x23, (byte) 0xf7, (byte) 0x86, + (byte) 0xbd, (byte) 0x3b, (byte) 0x86, (byte) 0xcc, (byte) 0xc3, (byte) 0x91, + (byte) 0xc0, (byte) 0x30, (byte) 0x32, (byte) 0x02, (byte) 0x47, (byte) 0x35, + (byte) 0x01, (byte) 0xef, (byte) 0xee, (byte) 0x98, (byte) 0x13, (byte) 0x56, + (byte) 0x49, (byte) 0x47, (byte) 0xb5, (byte) 0x20, (byte) 0xa8, (byte) 0x60, + (byte) 0xcb, (byte) 0xc0, (byte) 0xd5, (byte) 0x77, (byte) 0xc1, (byte) 0x69, + (byte) 0xcd, (byte) 0x18, (byte) 0x34, (byte) 0x92, (byte) 0xf2, (byte) 0x6a, + (byte) 0x2a, (byte) 0x10, (byte) 0x59, (byte) 0x1c, (byte) 0x91, (byte) 0x20, + (byte) 0x51, (byte) 0xca, (byte) 0x37, (byte) 0xb2, (byte) 0x87, (byte) 0xa6, + (byte) 0x8a, (byte) 0x02, (byte) 0xfd, (byte) 0x45, (byte) 0x46, (byte) 0xf9, + (byte) 0x76, (byte) 0xb1, (byte) 0x35, (byte) 0x38, (byte) 0x8d, (byte) 0xff, + (byte) 0x4c, (byte) 0x5d, (byte) 0x75, (byte) 0x8f, (byte) 0x66, (byte) 0x15, + (byte) 0x7d, (byte) 0x7b, (byte) 0xda, (byte) 0xdb, (byte) 0x57, (byte) 0x39, + (byte) 0xff, (byte) 0x91, (byte) 0x3f, (byte) 0xdd, (byte) 0xe2, (byte) 0xb4, + (byte) 0x22, (byte) 0x60, (byte) 0x4c, (byte) 0x32, (byte) 0x3b, (byte) 0x9d, + (byte) 0x34, (byte) 0x9f, (byte) 0xb9, (byte) 0x5d, (byte) 0x75, (byte) 0xb9, + (byte) 0xd3, (byte) 0x7f, (byte) 0x11, (byte) 0xba, (byte) 0xb7, (byte) 0xc8, + (byte) 0x32, (byte) 0xc6, (byte) 0xce, (byte) 0x71, (byte) 0x91, (byte) 0xd3, + (byte) 0x32, (byte) 0xaf, (byte) 0x4d, (byte) 0x7e, (byte) 0x7c, (byte) 0x15, + (byte) 0xf7, (byte) 0x71, (byte) 0x2c, (byte) 0x52, (byte) 0x65, (byte) 0x4d, + (byte) 0xa9, (byte) 0x81, (byte) 0x25, (byte) 0x35, (byte) 0xce, (byte) 0x0b, + (byte) 0x5b, (byte) 0x56, (byte) 0xfe, (byte) 0xf1, (byte) 0x02, (byte) 0x15, + (byte) 0x00, (byte) 0xeb, (byte) 0x4e, (byte) 0x7f, (byte) 0x7a, (byte) 0x31, + (byte) 0xb3, (byte) 0x7d, (byte) 0x8d, (byte) 0xb2, (byte) 0xf7, (byte) 0xaf, + (byte) 0xad, (byte) 0xb1, (byte) 0x42, (byte) 0x92, (byte) 0xf3, (byte) 0x6c, + (byte) 0xe4, (byte) 0xed, (byte) 0x8b, (byte) 0x02, (byte) 0x81, (byte) 0x81, + (byte) 0x00, (byte) 0x81, (byte) 0xc8, (byte) 0x36, (byte) 0x48, (byte) 0xdb, + (byte) 0x71, (byte) 0x2b, (byte) 0x91, (byte) 0xce, (byte) 0x6d, (byte) 0xbc, + (byte) 0xb8, (byte) 0xf9, (byte) 0xcb, (byte) 0x50, (byte) 0x91, (byte) 0x10, + (byte) 0x8a, (byte) 0xf8, (byte) 0x37, (byte) 0x50, (byte) 0xda, (byte) 0x4f, + (byte) 0xc8, (byte) 0x4d, (byte) 0x73, (byte) 0xcb, (byte) 0x4d, (byte) 0xb0, + (byte) 0x19, (byte) 0x54, (byte) 0x5a, (byte) 0xf3, (byte) 0x6c, (byte) 0xc9, + (byte) 0xd8, (byte) 0x96, (byte) 0xd9, (byte) 0xb0, (byte) 0x54, (byte) 0x7e, + (byte) 0x7d, (byte) 0xe2, (byte) 0x58, (byte) 0x0e, (byte) 0x5f, (byte) 0xc0, + (byte) 0xce, (byte) 0xb9, (byte) 0x5c, (byte) 0xe3, (byte) 0xd3, (byte) 0xdf, + (byte) 0xcf, (byte) 0x45, (byte) 0x74, (byte) 0xfb, (byte) 0xe6, (byte) 0x20, + (byte) 0xe7, (byte) 0xfc, (byte) 0x0f, (byte) 0xca, (byte) 0xdb, (byte) 0xc0, + (byte) 0x0b, (byte) 0xe1, (byte) 0x5a, (byte) 0x16, (byte) 0x1d, (byte) 0xb3, + (byte) 0x2e, (byte) 0xe5, (byte) 0x5f, (byte) 0x89, (byte) 0x17, (byte) 0x73, + (byte) 0x50, (byte) 0xd1, (byte) 0x4a, (byte) 0x60, (byte) 0xb7, (byte) 0xaa, + (byte) 0xf0, (byte) 0xc7, (byte) 0xc5, (byte) 0x03, (byte) 0x4e, (byte) 0x36, + (byte) 0x51, (byte) 0x9e, (byte) 0x2f, (byte) 0xfa, (byte) 0xf3, (byte) 0xd6, + (byte) 0x58, (byte) 0x14, (byte) 0x02, (byte) 0xb4, (byte) 0x41, (byte) 0xd6, + (byte) 0x72, (byte) 0x6f, (byte) 0x58, (byte) 0x5b, (byte) 0x2d, (byte) 0x23, + (byte) 0xc0, (byte) 0x75, (byte) 0x4f, (byte) 0x39, (byte) 0xa8, (byte) 0x6a, + (byte) 0xdf, (byte) 0x79, (byte) 0x21, (byte) 0xf2, (byte) 0x77, (byte) 0x91, + (byte) 0x3f, (byte) 0x1c, (byte) 0x4d, (byte) 0x48, (byte) 0x78, (byte) 0xcd, + (byte) 0xed, (byte) 0x79, (byte) 0x23, (byte) 0x03, (byte) 0x81, (byte) 0x84, + (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x1a, (byte) 0x50, + (byte) 0x9d, (byte) 0x3e, (byte) 0xa1, (byte) 0x6c, (byte) 0x99, (byte) 0x35, + (byte) 0x36, (byte) 0x26, (byte) 0x22, (byte) 0x6b, (byte) 0x47, (byte) 0x45, + (byte) 0x80, (byte) 0x5b, (byte) 0xd5, (byte) 0xc1, (byte) 0xc5, (byte) 0x70, + (byte) 0x75, (byte) 0x55, (byte) 0x66, (byte) 0x33, (byte) 0x1d, (byte) 0xae, + (byte) 0xd0, (byte) 0x01, (byte) 0x64, (byte) 0x8b, (byte) 0xae, (byte) 0x9d, + (byte) 0x66, (byte) 0x58, (byte) 0xf9, (byte) 0x42, (byte) 0x74, (byte) 0x3a, + (byte) 0x32, (byte) 0xc7, (byte) 0x7f, (byte) 0x25, (byte) 0x64, (byte) 0x7d, + (byte) 0x08, (byte) 0x26, (byte) 0xbf, (byte) 0x21, (byte) 0x3a, (byte) 0x84, + (byte) 0xcc, (byte) 0x2c, (byte) 0x66, (byte) 0x7d, (byte) 0xc7, (byte) 0xd6, + (byte) 0xb1, (byte) 0x69, (byte) 0x57, (byte) 0x67, (byte) 0x52, (byte) 0x73, + (byte) 0x3f, (byte) 0x79, (byte) 0x60, (byte) 0xaa, (byte) 0xf4, (byte) 0x8a, + (byte) 0x48, (byte) 0x42, (byte) 0x46, (byte) 0x41, (byte) 0xd0, (byte) 0x50, + (byte) 0x9b, (byte) 0xa2, (byte) 0x4e, (byte) 0xa5, (byte) 0x88, (byte) 0x10, + (byte) 0xf7, (byte) 0x61, (byte) 0xa2, (byte) 0xfa, (byte) 0x8d, (byte) 0xa6, + (byte) 0x13, (byte) 0x9e, (byte) 0x36, (byte) 0x86, (byte) 0x62, (byte) 0xf0, + (byte) 0x97, (byte) 0xef, (byte) 0x11, (byte) 0xc6, (byte) 0x35, (byte) 0xd3, + (byte) 0x79, (byte) 0x30, (byte) 0xde, (byte) 0xf2, (byte) 0x7f, (byte) 0x7a, + (byte) 0x3c, (byte) 0x03, (byte) 0xa3, (byte) 0xc5, (byte) 0xbc, (byte) 0xb1, + (byte) 0xbc, (byte) 0x2f, (byte) 0x10, (byte) 0xf4, (byte) 0x51, (byte) 0x89, + (byte) 0xe2, (byte) 0xaf, (byte) 0xf7, (byte) 0x61, (byte) 0x1a, (byte) 0xf0, + (byte) 0x87, (byte) 0x5e, (byte) 0xa5, (byte) 0x02, (byte) 0xd2, (byte) 0xe4, + (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30, (byte) 0x09, + (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, + (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c, (byte) 0x06, + (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x86, + (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04, (byte) 0x1f, + (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65, (byte) 0x6e, + (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47, (byte) 0x65, + (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74, (byte) 0x65, + (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72, (byte) 0x74, + (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61, (byte) 0x74, + (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, + (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, + (byte) 0xd1, (byte) 0x6c, (byte) 0x36, (byte) 0x36, (byte) 0x61, (byte) 0x6c, + (byte) 0xf6, (byte) 0x90, (byte) 0x82, (byte) 0x82, (byte) 0x87, (byte) 0x93, + (byte) 0xbe, (byte) 0x99, (byte) 0x60, (byte) 0x1b, (byte) 0x03, (byte) 0x58, + (byte) 0x36, (byte) 0x63, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, + (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30, + (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x47, (byte) 0x82, (byte) 0xa3, + (byte) 0xf1, (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, (byte) 0xde, (byte) 0x4f, + (byte) 0x30, (byte) 0x4c, (byte) 0x7f, (byte) 0x72, (byte) 0x81, (byte) 0x15, + (byte) 0x32, (byte) 0xda, (byte) 0x7f, (byte) 0x58, (byte) 0x18, (byte) 0x30, + (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, + (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, + (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, + (byte) 0x81, (byte) 0xde, (byte) 0x20, (byte) 0xa1, (byte) 0xb2, (byte) 0x50, + (byte) 0x03, (byte) 0xcd, (byte) 0x90, (byte) 0x4f, (byte) 0x2b, (byte) 0x47, + (byte) 0x1d, (byte) 0xac, (byte) 0x6e, (byte) 0xb4, (byte) 0xc7, (byte) 0x14, + (byte) 0xc6, (byte) 0x4f, (byte) 0x45, (byte) 0xaf, (byte) 0x81, (byte) 0x5d, + (byte) 0x5a, (byte) 0x31, (byte) 0xff, (byte) 0x9c, (byte) 0x4d, (byte) 0xdc, + (byte) 0x9e, (byte) 0x36, (byte) 0x9f, (byte) 0x9b, (byte) 0xb1, (byte) 0xc9, + (byte) 0x50, (byte) 0xa3, (byte) 0xf6, (byte) 0x9c, (byte) 0x68, (byte) 0x6f, + (byte) 0x68, (byte) 0xd9, (byte) 0x56, (byte) 0x1b, (byte) 0xe5, (byte) 0x1b, + (byte) 0x41, (byte) 0xd4, (byte) 0xcc, (byte) 0xb6, (byte) 0x37, (byte) 0xd5, + (byte) 0x69, (byte) 0x6b, (byte) 0x39, (byte) 0xaf, (byte) 0xc6, (byte) 0xb8, + (byte) 0x39, (byte) 0x76, (byte) 0xe3, (byte) 0xf7, (byte) 0x97, (byte) 0x74, + (byte) 0x31, (byte) 0xc4, (byte) 0x2d, (byte) 0xb7, (byte) 0x9a, (byte) 0xa4, + (byte) 0xfa, (byte) 0x9f, (byte) 0xa8, (byte) 0xe3, (byte) 0x41, (byte) 0xda, + (byte) 0x2f, (byte) 0x0c, (byte) 0x9d, (byte) 0x83, (byte) 0xdc, (byte) 0x86, + (byte) 0x1f, (byte) 0x5c, (byte) 0x0f, (byte) 0x87, (byte) 0x05, (byte) 0xc9, + (byte) 0xb0, (byte) 0x63, (byte) 0xca, (byte) 0x9b, (byte) 0xdb, (byte) 0xe6, + (byte) 0x3c, (byte) 0xe9, (byte) 0x23, (byte) 0x9e, (byte) 0x23, (byte) 0x44, + (byte) 0x1d, (byte) 0x5b, (byte) 0x60, (byte) 0x66, (byte) 0xb6, (byte) 0x72, + (byte) 0x8c, (byte) 0x87, (byte) 0x86, (byte) 0xe8, (byte) 0xdb, (byte) 0x29, + (byte) 0x67, (byte) 0x9c, (byte) 0x33, (byte) 0x5c, (byte) 0x39, (byte) 0xf1, + (byte) 0xb5, (byte) 0x9b, (byte) 0xb8, (byte) 0xe1, (byte) 0x42, (byte) 0x51, + (byte) 0xed, (byte) 0x2c + }; + /** * The amount of time to allow before and after expected time for variance * in timing tests. @@ -505,11 +1133,12 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertAliases(new String[] {}); assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, + null)); assertAliases(new String[] { TEST_ALIAS_1 }); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); @@ -533,11 +1162,12 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertAliases(new String[] {}); assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, + null)); assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); @@ -551,7 +1181,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2)); @@ -572,18 +1202,18 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // TEST_ALIAS_1 assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); // TEST_ALIAS_2 - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); // TEST_ALIAS_3 - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 }); @@ -617,10 +1247,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // TEST_ALIAS_1 assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); // Should not throw when a non-existent entry is requested for delete. @@ -632,7 +1262,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertAliases(new String[] { TEST_ALIAS_1 }); @@ -645,7 +1275,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { assertNotNull("Retrieved certificate should not be null", retrieved); CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); assertEquals("Actual and retrieved certificates should be the same", actual, retrieved); } @@ -664,11 +1294,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); assertEquals("Stored certificate alias should be found", TEST_ALIAS_1, mKeyStore.getCertificateAlias(actual)); @@ -681,14 +1311,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); assertEquals("Stored certificate alias should be found", TEST_ALIAS_1, mKeyStore.getCertificateAlias(actual)); @@ -701,19 +1331,19 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); // Insert TrustedCertificateEntry with CA name - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); // Insert PrivateKeyEntry that uses the same CA assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); assertEquals("Stored certificate alias should be found", TEST_ALIAS_2, mKeyStore.getCertificateAlias(actual)); @@ -726,7 +1356,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); assertNull("Stored certificate alias should not be found", mKeyStore.getCertificateAlias(actual)); @@ -737,11 +1367,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); CertificateFactory f = CertificateFactory.getInstance("X.509"); - Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); assertNull("Stored certificate alias should be found", mKeyStore.getCertificateAlias(userCert)); @@ -753,16 +1383,16 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); CertificateFactory cf = CertificateFactory.getInstance("X.509"); Certificate[] expected = new Certificate[2]; - expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1); @@ -792,10 +1422,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); Date now = new Date(); @@ -812,10 +1442,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); Date now = new Date(); @@ -833,7 +1463,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); Date now = new Date(); @@ -853,10 +1483,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); @@ -866,17 +1496,58 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; - assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); + } + + public void testKeyStore_GetEntry_DSA_NullParams_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_DSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, + FAKE_DSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_DSA_CA_1, + KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + + Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Entry should exist", entry); + + assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry); + + PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; + + assertPrivateKeyEntryEquals(keyEntry, "DSA", FAKE_DSA_KEY_1, FAKE_DSA_USER_1, FAKE_DSA_CA_1); } - public void testKeyStore_GetEntry_NullParams_Unencrypted_Success() throws Exception { + public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, + FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + + Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Entry should exist", entry); + + assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry); + + PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; + + assertPrivateKeyEntryEquals(keyEntry, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1); + } + + public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, + FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null); @@ -886,13 +1557,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; - assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } @SuppressWarnings("unchecked") - private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, byte[] key, byte[] cert, - byte[] ca) throws Exception { - KeyFactory keyFact = KeyFactory.getInstance("RSA"); + private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key, + byte[] cert, byte[] ca) throws Exception { + KeyFactory keyFact = KeyFactory.getInstance(keyType); PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key)); CertificateFactory certFact = CertificateFactory.getInstance("X.509"); @@ -911,9 +1583,19 @@ public class AndroidKeyStoreTest extends AndroidTestCase { private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey, Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception { - assertEquals("Returned PrivateKey should be what we inserted", - ((RSAPrivateKey) expectedKey).getModulus(), - ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus()); + if (expectedKey instanceof DSAPrivateKey) { + assertEquals("Returned PrivateKey should be what we inserted", + ((DSAPrivateKey) expectedKey).getParams(), + ((DSAPublicKey) keyEntry.getCertificate().getPublicKey()).getParams()); + } else if (expectedKey instanceof ECPrivateKey) { + assertEquals("Returned PrivateKey should be what we inserted", + ((ECPrivateKey) expectedKey).getParams().getCurve(), + ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve()); + } else if (expectedKey instanceof RSAPrivateKey) { + assertEquals("Returned PrivateKey should be what we inserted", + ((RSAPrivateKey) expectedKey).getModulus(), + ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus()); + } assertEquals("Returned Certificate should be what we inserted", expectedCert, keyEntry.getCertificate()); @@ -956,10 +1638,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); Key key = mKeyStore.getKey(TEST_ALIAS_1, null); @@ -970,7 +1652,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { RSAPrivateKey actualKey = (RSAPrivateKey) key; KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); assertEquals("Inserted key should be same as retrieved key", ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); @@ -980,10 +1662,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE)); Key key = mKeyStore.getKey(TEST_ALIAS_1, null); @@ -994,7 +1676,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { RSAPrivateKey actualKey = (RSAPrivateKey) key; KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); assertEquals("Inserted key should be same as retrieved key", ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus()); @@ -1005,7 +1687,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null)); @@ -1035,7 +1717,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { setupPassword(); mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertTrue("Should return true for CA certificate", @@ -1047,10 +1729,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertFalse("Should return false for PrivateKeyEntry", @@ -1077,10 +1759,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1)); @@ -1090,7 +1772,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { setupPassword(); mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1)); @@ -1106,7 +1788,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { public void testKeyStore_SetCertificate_CA_Encrypted_Success() throws Exception { final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); setupPassword(); mKeyStore.load(null, null); @@ -1124,13 +1806,13 @@ public class AndroidKeyStoreTest extends AndroidTestCase { setupPassword(); mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertAliases(new String[] { TEST_ALIAS_1 }); final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); // TODO have separate FAKE_CA for second test mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert); @@ -1143,16 +1825,16 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1, - FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1, + FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertAliases(new String[] { TEST_ALIAS_1 }); final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); try { mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert); @@ -1166,13 +1848,13 @@ public class AndroidKeyStoreTest extends AndroidTestCase { mKeyStore.load(null, null); KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final CertificateFactory f = CertificateFactory.getInstance("X.509"); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1186,20 +1868,74 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); } - public void testKeyStore_SetEntry_PrivateKeyEntry_Unencrypted_Success() throws Exception { + public void testKeyStore_SetEntry_PrivateKeyEntry_DSA_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("DSA"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_DSA_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_DSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_DSA_CA_1)); + + PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expected, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + + assertTrue("Retrieved entry should be of type PrivateKeyEntry", + actualEntry instanceof PrivateKeyEntry); + + PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; + + assertPrivateKeyEntryEquals(actual, "DSA", FAKE_DSA_KEY_1, FAKE_DSA_USER_1, FAKE_DSA_CA_1); + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception { + mKeyStore.load(null, null); + + KeyFactory keyFact = KeyFactory.getInstance("EC"); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1)); + + final CertificateFactory f = CertificateFactory.getInstance("X.509"); + + final Certificate[] expectedChain = new Certificate[2]; + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_CA_1)); + + PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); + + mKeyStore.setEntry(TEST_ALIAS_1, expected, null); + + Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null); + assertNotNull("Retrieved entry should exist", actualEntry); + + assertTrue("Retrieved entry should be of type PrivateKeyEntry", + actualEntry instanceof PrivateKeyEntry); + + PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; + + assertPrivateKeyEntryEquals(actual, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1); + } + + public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception { mKeyStore.load(null, null); KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final CertificateFactory f = CertificateFactory.getInstance("X.509"); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1213,20 +1949,20 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); } public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception { mKeyStore.load(null, null); KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final CertificateFactory f = CertificateFactory.getInstance("X.509"); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1253,11 +1989,11 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // Start with PrivateKeyEntry { - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1271,17 +2007,18 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } // TODO make entirely new test vector for the overwrite // Replace with PrivateKeyEntry { - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1295,7 +2032,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } } @@ -1308,7 +2046,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // Start with TrustedCertificateEntry { - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); @@ -1326,10 +2064,10 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // Replace with PrivateKeyEntry { KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); - expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); + expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1341,7 +2079,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { actualEntry instanceof PrivateKeyEntry); PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } } @@ -1352,14 +2091,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); // Start with PrivateKeyEntry { KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); expectedChain[1] = caCert; PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1372,7 +2111,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { actualEntry instanceof PrivateKeyEntry); PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } // Replace with TrustedCertificateEntry @@ -1400,14 +2140,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); // Start with PrivateKeyEntry { KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] expectedChain = new Certificate[2]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); expectedChain[1] = caCert; PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1420,15 +2160,16 @@ public class AndroidKeyStoreTest extends AndroidTestCase { actualEntry instanceof PrivateKeyEntry); PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } // Replace with PrivateKeyEntry that has no chain { KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] expectedChain = new Certificate[1]; - expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain); @@ -1440,7 +2181,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { actualEntry instanceof PrivateKeyEntry); PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, null); + assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + null); } } @@ -1453,7 +2195,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // Insert TrustedCertificateEntry { - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert); mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null); @@ -1471,7 +2213,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { // Replace with TrustedCertificateEntry of USER { final Certificate userCert = f - .generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert); mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null); @@ -1493,12 +2235,12 @@ public class AndroidKeyStoreTest extends AndroidTestCase { final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); chain[1] = caCert; try { @@ -1514,12 +2256,12 @@ public class AndroidKeyStoreTest extends AndroidTestCase { final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); chain[1] = caCert; mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); @@ -1532,7 +2274,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1); } public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception { @@ -1541,14 +2283,14 @@ public class AndroidKeyStoreTest extends AndroidTestCase { final CertificateFactory f = CertificateFactory.getInstance("X.509"); - final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1)); + final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1)); // Insert initial key { KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); chain[1] = caCert; mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); @@ -1561,16 +2303,17 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } // TODO make a separate key // Replace key { KeyFactory keyFact = KeyFactory.getInstance("RSA"); - PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1)); + PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)); final Certificate[] chain = new Certificate[2]; - chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1)); + chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1)); chain[1] = caCert; mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain); @@ -1583,7 +2326,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry; - assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1); + assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, + FAKE_RSA_CA_1); } } @@ -1635,7 +2379,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, - KeyStore.FLAG_ENCRYPTED)); + NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); Key key = mKeyStore.getKey(TEST_ALIAS_1, null); @@ -1691,7 +2435,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, - KeyStore.FLAG_ENCRYPTED)); + NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS); @@ -1704,7 +2448,7 @@ public class AndroidKeyStoreTest extends AndroidTestCase { { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2; assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, - KeyStore.FLAG_ENCRYPTED)); + NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2, TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS); @@ -1736,7 +2480,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; assertTrue(mAndroidKeyStore.generate(privateKeyAlias, - android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)); + android.security.KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, + android.security.KeyStore.FLAG_NONE, null)); X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1, @@ -1774,20 +2519,21 @@ public class AndroidKeyStoreTest extends AndroidTestCase { setupPassword(); mKeyStore.load(null, null); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertEquals("The keystore size should match expected", 1, mKeyStore.size()); assertAliases(new String[] { TEST_ALIAS_1 }); - assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1, + assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); assertEquals("The keystore size should match expected", 2, mKeyStore.size()); assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 }); assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3, - KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, + null)); assertEquals("The keystore size should match expected", 3, mKeyStore.size()); assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 }); @@ -1854,7 +2600,8 @@ public class AndroidKeyStoreTest extends AndroidTestCase { private void setupKey() throws Exception { final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1; assertTrue(mAndroidKeyStore - .generate(privateKeyAlias, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + .generate(privateKeyAlias, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, + KeyStore.FLAG_ENCRYPTED, null)); X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS); diff --git a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java index 113d730..bc8dd13 100644 --- a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java +++ b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java @@ -40,13 +40,17 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_Success() throws Exception { KeyPairGeneratorSpec spec = - new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, - NOW, NOW_PLUS_10_YEARS, 0); + new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, + SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0); assertEquals("Context should be the one specified", getContext(), spec.getContext()); assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias()); + assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType()); + + assertEquals("Key size should be the one specified", 1024, spec.getKeySize()); + assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN()); assertEquals("startDate should be the one specified", NOW, spec.getStartDate()); @@ -57,6 +61,8 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testBuilder_Success() throws Exception { KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext()) .setAlias(TEST_ALIAS_1) + .setKeyType("RSA") + .setKeySize(1024) .setSubject(TEST_DN_1) .setSerialNumber(SERIAL_1) .setStartDate(NOW) @@ -68,6 +74,10 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias()); + assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType()); + + assertEquals("Key size should be the one specified", 1024, spec.getKeySize()); + assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN()); assertEquals("startDate should be the one specified", NOW, spec.getStartDate()); @@ -79,7 +89,7 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullContext_Failure() throws Exception { try { - new KeyPairGeneratorSpec(null, TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, + new KeyPairGeneratorSpec(null, TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when context is null"); } catch (IllegalArgumentException success) { @@ -88,7 +98,7 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullKeystoreAlias_Failure() throws Exception { try { - new KeyPairGeneratorSpec(getContext(), null, TEST_DN_1, SERIAL_1, NOW, + new KeyPairGeneratorSpec(getContext(), null, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when keystoreAlias is null"); } catch (IllegalArgumentException success) { @@ -97,7 +107,7 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullSubjectDN_Failure() throws Exception { try { - new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, null, SERIAL_1, NOW, + new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when subjectDN is null"); } catch (IllegalArgumentException success) { @@ -106,7 +116,7 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullSerial_Failure() throws Exception { try { - new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, null, NOW, + new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW, NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when startDate is null"); } catch (IllegalArgumentException success) { @@ -115,8 +125,8 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullStartDate_Failure() throws Exception { try { - new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, null, - NOW_PLUS_10_YEARS, 0); + new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, + null, NOW_PLUS_10_YEARS, 0); fail("Should throw IllegalArgumentException when startDate is null"); } catch (IllegalArgumentException success) { } @@ -124,8 +134,8 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_NullEndDate_Failure() throws Exception { try { - new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW, - null, 0); + new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, + NOW, null, 0); fail("Should throw IllegalArgumentException when keystoreAlias is null"); } catch (IllegalArgumentException success) { } @@ -133,7 +143,7 @@ public class KeyPairGeneratorSpecTest extends AndroidTestCase { public void testConstructor_EndBeforeStart_Failure() throws Exception { try { - new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, + new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW_PLUS_10_YEARS, NOW, 0); fail("Should throw IllegalArgumentException when end is before start"); } catch (IllegalArgumentException success) { diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java index 9bf88d3..7a142cc 100644 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -22,6 +22,7 @@ import android.security.KeyStore; import android.test.ActivityUnitTestCase; import android.test.AssertionFailedError; import android.test.suitebuilder.annotation.MediumTest; +import com.android.org.conscrypt.NativeCrypto; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Date; @@ -347,21 +348,24 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { public void testGenerate_NotInitialized_Fail() throws Exception { assertFalse("Should fail when keystore is not initialized", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); } public void testGenerate_Locked_Fail() throws Exception { mKeyStore.password(TEST_PASSWD); mKeyStore.lock(); assertFalse("Should fail when keystore is locked", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); } public void testGenerate_Success() throws Exception { assertTrue(mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key when unlocked", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue(mKeyStore.contains(TEST_KEYNAME)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); } @@ -370,7 +374,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertTrue(mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key when unlocked", - mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); assertFalse(mKeyStore.contains(TEST_KEYNAME)); } @@ -378,7 +383,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception { assertTrue(mKeyStore.password(TEST_PASSWD)); - assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID, KeyStore.FLAG_ENCRYPTED)); + assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID, + NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); assertFalse(mKeyStore.contains(TEST_KEYNAME)); @@ -424,7 +430,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { public void testSign_Success() throws Exception { mKeyStore.password(TEST_PASSWD); - assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue(mKeyStore.contains(TEST_KEYNAME)); final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA); @@ -434,7 +441,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { public void testVerify_Success() throws Exception { mKeyStore.password(TEST_PASSWD); - assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue(mKeyStore.contains(TEST_KEYNAME)); final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA); @@ -461,7 +469,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key for testcase", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0)); @@ -494,7 +503,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key for testcase", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0)); @@ -527,7 +537,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key for testcase", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertFalse("Should not be able to revoke not existent grant", mKeyStore.ungrant(TEST_KEYNAME, 0)); @@ -538,7 +549,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key for testcase", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0)); @@ -555,7 +567,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { mKeyStore.password(TEST_PASSWD)); assertTrue("Should be able to generate key for testcase", - mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0)); @@ -575,7 +588,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertFalse(mKeyStore.contains(TEST_KEYNAME)); - assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue(mKeyStore.contains(TEST_KEYNAME)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID)); @@ -613,7 +627,8 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertFalse(mKeyStore.contains(TEST_KEYNAME)); - assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED)); + assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, + 1024, KeyStore.FLAG_ENCRYPTED, null)); assertTrue(mKeyStore.contains(TEST_KEYNAME)); assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID)); diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 4dcac31..58f5d55 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -294,14 +294,13 @@ public class RemoteControlClient */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; /** - * @hide - * CANDIDATE FOR PUBLIC API * Flag indicating a RemoteControlClient supports ratings. * This flag must be set in order for components that display the RemoteControlClient * information, to display ratings information, and, if ratings are declared editable * (by calling {@link MetadataEditor#addEditableKey(int)} with the * {@link MetadataEditor#LONG_KEY_RATING_BY_USER} key), it will enable the user to rate - * the media. + * the media, with values being received through the interface set with + * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}. * @see #setTransportControlFlags(int) */ public final static int FLAG_KEY_MEDIA_RATING = 1 << 9; @@ -452,8 +451,6 @@ public class RemoteControlClient */ public final static int BITMAP_KEY_ARTWORK = 100; /** - * @hide - * CANDIDATE FOR PUBLIC API * The metadata key qualifying the content rating. * The value associated with this key may be: {@link #RATING_HEART}, * {@link #RATING_THUMB_UP_DOWN}, or a non-null positive integer expressing a maximum @@ -461,8 +458,6 @@ public class RemoteControlClient */ public final static int LONG_KEY_RATING_TYPE = 101; /** - * @hide - * CANDIDATE FOR PUBLIC API * The metadata key for the content's average rating, not the user's rating. * The value associated with this key may be: an integer value between 0 and 100, * or {@link #RATING_NOT_RATED} to express that no average rating is available. @@ -472,11 +467,12 @@ public class RemoteControlClient * <p></p> * When the rating type is: * <ul> - * <li>{@link #RATING_HEART}, a rating of 50 to 100 means "heart selected",</li> - * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 49 means "thumb down", 50 means - * both "thumb up" and "thumb down" selected, 51 to 100 means "thumb up"</li> + * <li>{@link #RATING_HEART}, a rating of 51 to 100 means "heart selected",</li> + * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 50 means "thumb down", + * 51 to 100 means "thumb up"</li> * <li>a non-null positive integer, the rating value is mapped to the number of stars, e.g. - * with a maximum of 5 stars, a rating of 21 to 40 maps to 2 stars.</li> + * with a maximum of 5 stars, a rating of 0 maps to 0 stars, 1 to 20 maps to 1 star, + * 21 to 40 maps to 2 stars, etc.</li> * </ul> * @see #LONG_KEY_RATING_BY_USER */ @@ -489,34 +485,29 @@ public class RemoteControlClient */ public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF; /** - * @hide - * CANDIDATE FOR PUBLIC API - * The metadata key for the content's rating by the user. + * The metadata key for the content's user rating. * The value associated with this key may be: an integer value between 0 and 100, * or {@link #RATING_NOT_RATED} to express that the user hasn't rated this content. * Rules for the interpretation of the rating value according to the rating style are - * the same as for {@link #LONG_KEY_RATING_BY_OTHERS} + * the same as for {@link #LONG_KEY_RATING_BY_OTHERS}. + * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable + * receiving user rating values through the + * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface. */ public final static int LONG_KEY_RATING_BY_USER = 0x10000001; /** - * @hide - * CANDIDATE FOR PUBLIC API * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to * indicate the content referred to is a favorite (or not). * @see #LONG_KEY_RATING_TYPE */ public final static long RATING_HEART = -1; /** - * @hide - * CANDIDATE FOR PUBLIC API * A rating style for "thumb up" vs "thumb down". * @see #LONG_KEY_RATING_TYPE */ public final static long RATING_THUMB_UP_DOWN = -2; /** - * @hide - * CANDIDATE FOR PUBLIC API * A rating value indicating no rating is available. * @see #LONG_KEY_RATING_BY_OTHERS * @see #LONG_KEY_RATING_BY_USER @@ -573,7 +564,9 @@ public class RemoteControlClient * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value * expressed in milliseconds), - * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}, + * {@link #LONG_KEY_RATING_BY_OTHERS}, {@link #LONG_KEY_RATING_BY_USER}, + * {@link #LONG_KEY_RATING_TYPE}. * @param value The long value for the given key * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. @@ -620,8 +613,9 @@ public class RemoteControlClient /** * Clears all the metadata that has been set since the MetadataEditor instance was * created with {@link RemoteControlClient#editMetadata(boolean)}. + * Note that clearing the metadata doesn't reset the editable keys + * (use {@link #clearEditableKeys()} instead). */ - // TODO add in javadoc that this doesn't call clearEditableKeys() public synchronized void clear() { if (mApplied) { Log.e(TAG, "Can't clear a previously applied MetadataEditor"); @@ -632,8 +626,11 @@ public class RemoteControlClient } /** - * @hide - * CANDIDATE FOR PUBLIC API + * Flag the given key as being editable. + * This will declare the metadata field as eligible to be updated, with new values + * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface. + * @param key the type of metadata that can be edited. The supported key is + * {@link #LONG_KEY_RATING_BY_USER}. */ public synchronized void addEditableKey(int key) { if (mApplied) { @@ -651,8 +648,7 @@ public class RemoteControlClient } /** - * @hide - * CANDIDATE FOR PUBLIC API + * Causes all metadata fields to be read-only. */ public synchronized void clearEditableKeys() { if (mApplied) { @@ -869,7 +865,8 @@ public class RemoteControlClient * {@link #FLAG_KEY_MEDIA_STOP}, * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, * {@link #FLAG_KEY_MEDIA_NEXT}, - * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE} + * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}, + * {@link #FLAG_KEY_MEDIA_RATING}. */ public void setTransportControlFlags(int transportControlFlags) { synchronized(mCacheLock) { @@ -882,36 +879,39 @@ public class RemoteControlClient } /** - * @hide - * CANDIDATE FOR PUBLIC API - * TODO ADD DESCRIPTION + * Interface definition for a callback to be invoked when one of the metadata values has + * been updated. */ public interface OnMetadataUpdateListener { /** - * TODO ADD DESCRIPTION - * @param key - * @param newValue + * Called on the implementer to notify that the metadata field for the given key has + * been updated to the new value of type <code>long</long>. + * @param key the identifier of the updated metadata field of type <code>long</long>. + * @param newValue the new <code>long</long> value for the key */ void onMetadataUpdateLong(int key, long newValue); /** - * TODO ADD DESCRIPTION - * @param key - * @param newValue + * Called on the implementer to notify that the metadata field for the given key has + * been updated to the new <code>String</long>. + * @param key the identifier of the updated metadata field of type <code>String</long>. + * @param newValue the new <code>String</long> value for the key */ void onMetadataUpdateString(int key, String newValue); /** - * TODO ADD DESCRIPTION - * @param key - * @param newValue + * Called on the implementer to notify that the metadata field for the given key has + * been updated to the new {@link android.graphics.Bitmap}. + * @param key the identifier of the updated metadata field of type + * {@link android.graphics.Bitmap}. + * @param newValue the new {@link android.graphics.Bitmap} for the key */ void onMetadataUpdateBitmap(int key, Bitmap newValue); } /** - * @hide - * CANDIDATE FOR PUBLIC API - * TODO ADD DESCRIPTION - * @param l + * Sets the listener to be called whenever the metadata is updated. + * New metadata values will be received in the same thread as the one in which + * RemoteControlClient was created. + * @param l the metadata update listener */ public void setMetadataUpdateListener(OnMetadataUpdateListener l) { synchronized(mCacheLock) { diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk index 853353d..7900953 100644 --- a/packages/DocumentsUI/Android.mk +++ b/packages/DocumentsUI/Android.mk @@ -5,7 +5,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 guava LOCAL_PACKAGE_NAME := DocumentsUI LOCAL_CERTIFICATE := platform diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 6cc92e3..45e2650 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -11,10 +11,7 @@ <!-- TODO: allow rotation when state saving is in better shape --> <activity android:name=".DocumentsActivity" - android:finishOnCloseSystemDialogs="true" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.Holo.Light" - android:screenOrientation="nosensor"> + android:theme="@android:style/Theme.Holo.Light"> <intent-filter android:priority="100"> <action android:name="android.intent.action.OPEN_DOCUMENT" /> <category android:name="android.intent.category.DEFAULT" /> @@ -37,7 +34,7 @@ <intent-filter> <action android:name="android.provider.action.MANAGE_DOCUMENTS" /> <category android:name="android.intent.category.DEFAULT" /> - <data android:mimeType="vnd.android.doc/dir" /> + <data android:mimeType="vnd.android.document/directory" /> </intent-filter> </activity> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 928ba85..f4a822d 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -63,4 +63,6 @@ <string name="more">More</string> <string name="loading">Loading\u2026</string> + <string name="share_via">Share via</string> + </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index 6bc554f..e0b8d19 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -20,7 +20,6 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -28,13 +27,13 @@ import android.content.DialogInterface.OnClickListener; import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.Toast; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; /** * Dialog to create a new directory. @@ -67,24 +66,17 @@ public class CreateDirectoryFragment extends DialogFragment { final String displayName = text1.getText().toString(); final DocumentsActivity activity = (DocumentsActivity) getActivity(); - final Document cwd = activity.getCurrentDirectory(); + final DocumentInfo cwd = activity.getCurrentDirectory(); - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - cwd.uri.getAuthority()); try { - final String docId = DocumentsContract.createDocument(client, - DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR, - displayName); + final Uri childUri = DocumentsContract.createDocument( + resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName); // Navigate into newly created child - final Uri childUri = DocumentsContract.buildDocumentUri( - cwd.uri.getAuthority(), docId); - final Document childDoc = Document.fromUri(resolver, childUri); + final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri); activity.onDocumentPicked(childDoc); } catch (Exception e) { Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show(); - } finally { - ContentProviderClient.closeQuietly(client); } } }); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 783b6ff..5b23ca5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -17,12 +17,12 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST; -import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DISPLAY_NAME; -import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED; -import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE; +import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; +import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; +import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; +import static com.android.documentsui.model.DocumentInfo.getCursorInt; +import static com.android.documentsui.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; import android.app.Fragment; import android.app.FragmentManager; @@ -32,12 +32,14 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.Loader; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Document; import android.text.format.DateUtils; import android.text.format.Formatter; import android.text.format.Time; @@ -60,13 +62,13 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -import com.android.documentsui.DocumentsActivity.DisplayState; -import com.android.documentsui.model.Document; +import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.model.DocumentInfo; +import com.android.documentsui.model.RootInfo; import com.android.internal.util.Predicate; import com.google.android.collect.Lists; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -81,7 +83,7 @@ public class DirectoryFragment extends Fragment { private AbsListView mCurrentView; - private Predicate<Document> mFilter; + private Predicate<DocumentInfo> mFilter; public static final int TYPE_NORMAL = 1; public static final int TYPE_SEARCH = 2; @@ -95,30 +97,38 @@ public class DirectoryFragment extends Fragment { private LoaderCallbacks<DirectoryResult> mCallbacks; private static final String EXTRA_TYPE = "type"; - private static final String EXTRA_URI = "uri"; + private static final String EXTRA_AUTHORITY = "authority"; + private static final String EXTRA_ROOT_ID = "rootId"; + private static final String EXTRA_DOC_ID = "docId"; + private static final String EXTRA_QUERY = "query"; private static AtomicInteger sLoaderId = new AtomicInteger(4000); + private int mLastSortOrder = -1; + private final int mLoaderId = sLoaderId.incrementAndGet(); public static void showNormal(FragmentManager fm, Uri uri) { - show(fm, TYPE_NORMAL, uri); + show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null); } public static void showSearch(FragmentManager fm, Uri uri, String query) { - final Uri searchUri = DocumentsContract.buildSearchUri( - uri.getAuthority(), DocumentsContract.getDocId(uri), query); - show(fm, TYPE_SEARCH, searchUri); + show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), + query); } public static void showRecentsOpen(FragmentManager fm) { - show(fm, TYPE_RECENT_OPEN, null); + show(fm, TYPE_RECENT_OPEN, null, null, null, null); } - private static void show(FragmentManager fm, int type, Uri uri) { + private static void show(FragmentManager fm, int type, String authority, String rootId, + String docId, String query) { final Bundle args = new Bundle(); args.putInt(EXTRA_TYPE, type); - args.putParcelable(EXTRA_URI, uri); + args.putString(EXTRA_AUTHORITY, authority); + args.putString(EXTRA_ROOT_ID, rootId); + args.putString(EXTRA_DOC_ID, docId); + args.putString(EXTRA_QUERY, query); final DirectoryFragment fragment = new DirectoryFragment(); fragment.setArguments(args); @@ -137,7 +147,6 @@ public class DirectoryFragment extends Fragment { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Context context = inflater.getContext(); - final View view = inflater.inflate(R.layout.fragment_directory, container, false); mEmptyView = view.findViewById(android.R.id.empty); @@ -150,80 +159,77 @@ public class DirectoryFragment extends Fragment { mGridView.setOnItemClickListener(mItemListener); mGridView.setMultiChoiceModeListener(mMultiListener); - mAdapter = new DocumentsAdapter(); + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); - final Uri uri = getArguments().getParcelable(EXTRA_URI); + final Context context = getActivity(); + + mAdapter = new DocumentsAdapter(); mType = getArguments().getInt(EXTRA_TYPE); mCallbacks = new LoaderCallbacks<DirectoryResult>() { @Override public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) { - final DisplayState state = getDisplayState(DirectoryFragment.this); - mFilter = new MimePredicate(state.acceptMimes); + final State state = getDisplayState(DirectoryFragment.this); + + final String authority = getArguments().getString(EXTRA_AUTHORITY); + final String rootId = getArguments().getString(EXTRA_ROOT_ID); + final String docId = getArguments().getString(EXTRA_DOC_ID); + final String query = getArguments().getString(EXTRA_QUERY); Uri contentsUri; - if (mType == TYPE_NORMAL) { - contentsUri = DocumentsContract.buildChildrenUri( - uri.getAuthority(), DocumentsContract.getDocId(uri)); - } else if (mType == TYPE_RECENT_OPEN) { - contentsUri = RecentsProvider.buildRecentOpen(); - } else { - contentsUri = uri; - } + switch (mType) { + case TYPE_NORMAL: + contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId); + return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder); + case TYPE_SEARCH: + contentsUri = DocumentsContract.buildSearchDocumentsUri( + authority, docId, query); + return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder); + case TYPE_RECENT_OPEN: + final RootsCache roots = DocumentsApplication.getRootsCache(context); + final List<RootInfo> matchingRoots = roots.getMatchingRoots(state); + return new RecentLoader(context, matchingRoots); + default: + throw new IllegalStateException("Unknown type " + mType); - final Comparator<Document> sortOrder; - if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) { - sortOrder = new Document.LastModifiedComparator(); - } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) { - sortOrder = new Document.DisplayNameComparator(); - } else if (state.sortOrder == SORT_ORDER_SIZE) { - sortOrder = new Document.SizeComparator(); - } else { - throw new IllegalArgumentException("Unknown sort order " + state.sortOrder); } - - return new DirectoryLoader(context, contentsUri, mType, null, sortOrder); } @Override public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) { - mAdapter.swapDocuments(result.contents); + mAdapter.swapCursor(result.cursor); } @Override public void onLoaderReset(Loader<DirectoryResult> loader) { - mAdapter.swapDocuments(null); + mAdapter.swapCursor(null); } }; updateDisplayState(); - - return view; - } - - @Override - public void onStart() { - super.onStart(); - getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks); - } - - @Override - public void onStop() { - super.onStop(); - getLoaderManager().destroyLoader(mLoaderId); } public void updateDisplayState() { - final DisplayState state = getDisplayState(this); + final State state = getDisplayState(this); + + if (mLastSortOrder != state.sortOrder) { + getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); + mLastSortOrder = state.sortOrder; + } - // TODO: avoid kicking loader when nothing changed - getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks); mListView.smoothScrollToPosition(0); mGridView.smoothScrollToPosition(0); mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE); mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE); + mFilter = new MimePredicate(state.acceptMimes); + final int choiceMode; if (state.allowMultiple) { choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; @@ -258,7 +264,8 @@ public class DirectoryFragment extends Fragment { private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Document doc = mAdapter.getItem(position); + final Cursor cursor = mAdapter.getItem(position); + final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); if (mFilter.apply(doc)) { ((DocumentsActivity) getActivity()).onDocumentPicked(doc); } @@ -274,7 +281,7 @@ public class DirectoryFragment extends Fragment { @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - final DisplayState state = getDisplayState(DirectoryFragment.this); + final State state = getDisplayState(DirectoryFragment.this); final MenuItem open = menu.findItem(R.id.menu_open); final MenuItem share = menu.findItem(R.id.menu_share); @@ -291,11 +298,12 @@ public class DirectoryFragment extends Fragment { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); - final ArrayList<Document> docs = Lists.newArrayList(); + final ArrayList<DocumentInfo> docs = Lists.newArrayList(); final int size = checked.size(); for (int i = 0; i < size; i++) { if (checked.valueAt(i)) { - final Document doc = mAdapter.getItem(checked.keyAt(i)); + final Cursor cursor = mAdapter.getItem(checked.keyAt(i)); + final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); docs.add(doc); } } @@ -303,14 +311,17 @@ public class DirectoryFragment extends Fragment { final int id = item.getItemId(); if (id == R.id.menu_open) { DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); + mode.finish(); return true; } else if (id == R.id.menu_share) { onShareDocuments(docs); + mode.finish(); return true; } else if (id == R.id.menu_delete) { onDeleteDocuments(docs); + mode.finish(); return true; } else { @@ -328,8 +339,9 @@ public class DirectoryFragment extends Fragment { ActionMode mode, int position, long id, boolean checked) { if (checked) { // Directories cannot be checked - final Document doc = mAdapter.getItem(position); - if (doc.isDirectory()) { + final Cursor cursor = mAdapter.getItem(position); + final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + if (Document.MIME_TYPE_DIR.equals(docMimeType)) { mCurrentView.setItemChecked(position, false); } } @@ -339,36 +351,46 @@ public class DirectoryFragment extends Fragment { } }; - private void onShareDocuments(List<Document> docs) { - final ArrayList<Uri> uris = Lists.newArrayList(); - for (Document doc : docs) { - uris.add(doc.uri); - } + private void onShareDocuments(List<DocumentInfo> docs) { + Intent intent; + if (docs.size() == 1) { + final DocumentInfo doc = docs.get(0); - final Intent intent; - if (uris.size() > 1) { + intent = new Intent(Intent.ACTION_SEND); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setType(doc.mimeType); + intent.putExtra(Intent.EXTRA_STREAM, doc.uri); + + } else if (docs.size() > 1) { intent = new Intent(Intent.ACTION_SEND_MULTIPLE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addCategory(Intent.CATEGORY_DEFAULT); - // TODO: find common mimetype - intent.setType("*/*"); + + final ArrayList<String> mimeTypes = Lists.newArrayList(); + final ArrayList<Uri> uris = Lists.newArrayList(); + for (DocumentInfo doc : docs) { + mimeTypes.add(doc.mimeType); + uris.add(doc.uri); + } + + intent.setType(findCommonMimeType(mimeTypes)); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + } else { - intent = new Intent(Intent.ACTION_SEND); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setData(uris.get(0)); + return; } + intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via)); startActivity(intent); } - private void onDeleteDocuments(List<Document> docs) { + private void onDeleteDocuments(List<DocumentInfo> docs) { final Context context = getActivity(); final ContentResolver resolver = context.getContentResolver(); boolean hadTrouble = false; - for (Document doc : docs) { + for (DocumentInfo doc : docs) { if (!doc.isDeleteSupported()) { Log.w(TAG, "Skipping " + doc); hadTrouble = true; @@ -391,20 +413,17 @@ public class DirectoryFragment extends Fragment { } } - private static DisplayState getDisplayState(Fragment fragment) { + private static State getDisplayState(Fragment fragment) { return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); } private class DocumentsAdapter extends BaseAdapter { - private List<Document> mDocuments; - - public DocumentsAdapter() { - } + private Cursor mCursor; - public void swapDocuments(List<Document> documents) { - mDocuments = documents; + public void swapCursor(Cursor cursor) { + mCursor = cursor; - if (mDocuments != null && mDocuments.isEmpty()) { + if (isEmpty()) { mEmptyView.setVisibility(View.VISIBLE); } else { mEmptyView.setVisibility(View.GONE); @@ -416,7 +435,7 @@ public class DirectoryFragment extends Fragment { @Override public View getView(int position, View convertView, ViewGroup parent) { final Context context = parent.getContext(); - final DisplayState state = getDisplayState(DirectoryFragment.this); + final State state = getDisplayState(DirectoryFragment.this); final RootsCache roots = DocumentsApplication.getRootsCache(context); final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( @@ -433,7 +452,18 @@ public class DirectoryFragment extends Fragment { } } - final Document doc = getItem(position); + final Cursor cursor = getItem(position); + + final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); + final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID); + final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); + final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); + final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON); + final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); + final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY); + final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE); final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); final TextView title = (TextView) convertView.findViewById(android.R.id.title); @@ -448,32 +478,39 @@ public class DirectoryFragment extends Fragment { oldTask.cancel(false); } - if (doc.isThumbnailSupported()) { - final Bitmap cachedResult = thumbs.get(doc.uri); + if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) { + final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); + final Bitmap cachedResult = thumbs.get(uri); if (cachedResult != null) { icon.setImageBitmap(cachedResult); } else { final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize); icon.setImageBitmap(null); icon.setTag(task); - task.execute(doc.uri); + task.execute(uri); } + } else if (docIcon != 0) { + icon.setImageDrawable(DocumentInfo.loadIcon(context, docAuthority, docIcon)); } else { - icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, doc.mimeType)); + icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, docMimeType)); } - title.setText(doc.displayName); + title.setText(docDisplayName); - if (mType == TYPE_NORMAL || mType == TYPE_SEARCH) { + if (mType == TYPE_RECENT_OPEN) { + final RootInfo root = roots.getRoot(docAuthority, docRootId); + icon1.setVisibility(View.VISIBLE); + icon1.setImageDrawable(root.loadIcon(context)); + summary.setText(root.getDirectoryString()); + summary.setVisibility(View.VISIBLE); + } else { icon1.setVisibility(View.GONE); - if (doc.summary != null) { - summary.setText(doc.summary); + if (docSummary != null) { + summary.setText(docSummary); summary.setVisibility(View.VISIBLE); } else { summary.setVisibility(View.INVISIBLE); } - } else if (mType == TYPE_RECENT_OPEN) { - // TODO: resolve storage root } if (summaryGrid != null) { @@ -481,18 +518,18 @@ public class DirectoryFragment extends Fragment { (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE); } - if (doc.lastModified == -1) { + if (docLastModified == -1) { date.setText(null); } else { - date.setText(formatTime(context, doc.lastModified)); + date.setText(formatTime(context, docLastModified)); } if (state.showSize) { size.setVisibility(View.VISIBLE); - if (doc.isDirectory() || doc.size == -1) { + if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) { size.setText(null); } else { - size.setText(Formatter.formatFileSize(context, doc.size)); + size.setText(Formatter.formatFileSize(context, docSize)); } } else { size.setVisibility(View.GONE); @@ -503,17 +540,20 @@ public class DirectoryFragment extends Fragment { @Override public int getCount() { - return mDocuments != null ? mDocuments.size() : 0; + return mCursor != null ? mCursor.getCount() : 0; } @Override - public Document getItem(int position) { - return mDocuments.get(position); + public Cursor getItem(int position) { + if (mCursor != null) { + mCursor.moveToPosition(position); + } + return mCursor; } @Override public long getItemId(int position) { - return getItem(position).uri.hashCode(); + return position; } } @@ -538,8 +578,8 @@ public class DirectoryFragment extends Fragment { Bitmap result = null; try { - result = DocumentsContract.getThumbnail( - context.getContentResolver(), uri, mThumbSize); + result = DocumentsContract.getDocumentThumbnail( + context.getContentResolver(), uri, mThumbSize, null); if (result != null) { final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( context, mThumbSize); @@ -580,4 +620,28 @@ public class DirectoryFragment extends Fragment { return DateUtils.formatDateTime(context, when, flags); } + + private String findCommonMimeType(List<String> mimeTypes) { + String[] commonType = mimeTypes.get(0).split("/"); + if (commonType.length != 2) { + return "*/*"; + } + + for (int i = 1; i < mimeTypes.size(); i++) { + String[] type = mimeTypes.get(i).split("/"); + if (type.length != 2) continue; + + if (!commonType[1].equals(type[1])) { + commonType[1] = "*"; + } + + if (!commonType[0].equals(type[0])) { + commonType[0] = "*"; + commonType[1] = "*"; + break; + } + } + + return commonType[0] + "/" + commonType[1]; + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 4ce5ef8..3f016b5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -16,98 +16,155 @@ package com.android.documentsui; -import static com.android.documentsui.DirectoryFragment.TYPE_NORMAL; -import static com.android.documentsui.DirectoryFragment.TYPE_RECENT_OPEN; -import static com.android.documentsui.DirectoryFragment.TYPE_SEARCH; -import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; -import android.content.ContentResolver; +import android.content.AsyncTaskLoader; +import android.content.ContentProviderClient; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; -import android.util.Log; - -import com.android.documentsui.model.Document; -import com.android.internal.util.Predicate; -import com.google.android.collect.Lists; +import android.os.OperationCanceledException; +import android.provider.DocumentsContract.Document; import libcore.io.IoUtils; -import java.io.FileNotFoundException; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - class DirectoryResult implements AutoCloseable { + ContentProviderClient client; Cursor cursor; - List<Document> contents = Lists.newArrayList(); - Exception e; + Exception exception; @Override - public void close() throws Exception { + public void close() { IoUtils.closeQuietly(cursor); + ContentProviderClient.closeQuietly(client); + cursor = null; + client = null; } } -public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> { +public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { + private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); + + private final String mRootId; + private final Uri mUri; + private final int mSortOrder; - private final int mType; - private Predicate<Document> mFilter; - private Comparator<Document> mSortOrder; + private CancellationSignal mSignal; + private DirectoryResult mResult; - public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter, - Comparator<Document> sortOrder) { - super(context, uri); - mType = type; - mFilter = filter; + public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) { + super(context); + mRootId = rootId; + mUri = uri; mSortOrder = sortOrder; } @Override - public DirectoryResult loadInBackground(Uri uri, CancellationSignal signal) { + public final DirectoryResult loadInBackground() { + synchronized (this) { + if (isLoadInBackgroundCanceled()) { + throw new OperationCanceledException(); + } + mSignal = new CancellationSignal(); + } final DirectoryResult result = new DirectoryResult(); + final String authority = mUri.getAuthority(); try { - loadInBackgroundInternal(result, uri, signal); + result.client = getContext() + .getContentResolver().acquireUnstableContentProviderClient(authority); + final Cursor cursor = result.client.query( + mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal); + final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1); + final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder); + + result.cursor = sorted; + result.cursor.registerContentObserver(mObserver); } catch (Exception e) { - result.e = e; + result.exception = e; + ContentProviderClient.closeQuietly(result.client); + } finally { + synchronized (this) { + mSignal = null; + } } return result; } - private void loadInBackgroundInternal( - DirectoryResult result, Uri uri, CancellationSignal signal) throws RuntimeException { - // TODO: switch to using unstable CPC - final ContentResolver resolver = getContext().getContentResolver(); - final Cursor cursor = resolver.query(uri, null, null, null, null, signal); - result.cursor = cursor; - result.cursor.registerContentObserver(mObserver); - - while (cursor.moveToNext()) { - Document doc = null; - switch (mType) { - case TYPE_NORMAL: - case TYPE_SEARCH: - doc = Document.fromDirectoryCursor(uri, cursor); - break; - case TYPE_RECENT_OPEN: - try { - doc = Document.fromRecentOpenCursor(resolver, cursor); - } catch (FileNotFoundException e) { - Log.w(TAG, "Failed to find recent: " + e); - } - break; - default: - throw new IllegalArgumentException("Unknown type"); - } + @Override + public void cancelLoadInBackground() { + super.cancelLoadInBackground(); - if (doc != null && (mFilter == null || mFilter.apply(doc))) { - result.contents.add(doc); + synchronized (this) { + if (mSignal != null) { + mSignal.cancel(); } } + } + + @Override + public void deliverResult(DirectoryResult result) { + if (isReset()) { + IoUtils.closeQuietly(result); + return; + } + DirectoryResult oldResult = mResult; + mResult = result; + + if (isStarted()) { + super.deliverResult(result); + } + + if (oldResult != null && oldResult != result) { + IoUtils.closeQuietly(oldResult); + } + } + + @Override + protected void onStartLoading() { + if (mResult != null) { + deliverResult(mResult); + } + if (takeContentChanged() || mResult == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(DirectoryResult result) { + IoUtils.closeQuietly(result); + } + + @Override + protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped + onStopLoading(); + + IoUtils.closeQuietly(mResult); + mResult = null; + + getContext().getContentResolver().unregisterContentObserver(mObserver); + } - if (mSortOrder != null) { - Collections.sort(result.contents, mSortOrder); + public static String getQuerySortOrder(int sortOrder) { + switch (sortOrder) { + case SORT_ORDER_DISPLAY_NAME: + return Document.COLUMN_DISPLAY_NAME + " ASC"; + case SORT_ORDER_LAST_MODIFIED: + return Document.COLUMN_LAST_MODIFIED + " DESC"; + case SORT_ORDER_SIZE: + return Document.COLUMN_SIZE + " DESC"; + default: + return null; } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java index 0ce5968..54f62ef 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java @@ -21,11 +21,10 @@ import static com.android.documentsui.DocumentsActivity.TAG; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.provider.DocumentsContract.DocumentRoot; import android.util.Log; /** - * Handles {@link DocumentRoot} changes which invalidate cached data. + * Handles changes which invalidate cached data. */ public class DocumentChangedReceiver extends BroadcastReceiver { @Override diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 73ca8fa..f569f5a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -16,13 +16,13 @@ package com.android.documentsui; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_CREATE; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_GET_CONTENT; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_OPEN; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST; -import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED; +import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE; +import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; +import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; +import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN; +import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; +import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; import android.app.ActionBar; import android.app.ActionBar.OnNavigationListener; @@ -41,8 +41,8 @@ import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; +import android.os.Parcel; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentRoot; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; @@ -60,32 +60,29 @@ import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; import android.widget.Toast; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.DurableUtils; +import com.android.documentsui.model.RootInfo; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Arrays; import java.util.List; public class DocumentsActivity extends Activity { public static final String TAG = "Documents"; - private int mAction; - private SearchView mSearchView; private View mRootsContainer; private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; - private final DisplayState mDisplayState = new DisplayState(); + private static final String EXTRA_STATE = "state"; private RootsCache mRoots; - - /** Current user navigation stack; empty implies recents. */ - private DocumentStack mStack = new DocumentStack(); - /** Currently active search, overriding any stack. */ - private String mCurrentSearch; + private State mState; @Override public void onCreate(Bundle icicle) { @@ -93,74 +90,86 @@ public class DocumentsActivity extends Activity { mRoots = DocumentsApplication.getRootsCache(this); - final Intent intent = getIntent(); - final String action = intent.getAction(); - if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { - mAction = ACTION_OPEN; - } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { - mAction = ACTION_CREATE; - } else if (Intent.ACTION_GET_CONTENT.equals(action)) { - mAction = ACTION_GET_CONTENT; - } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) { - mAction = ACTION_MANAGE; - } + setResult(Activity.RESULT_CANCELED); + setContentView(R.layout.activity); - // TODO: unify action in single place - mDisplayState.action = mAction; + mRootsContainer = findViewById(R.id.container_roots); - if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { - mDisplayState.allowMultiple = intent.getBooleanExtra( - Intent.EXTRA_ALLOW_MULTIPLE, false); - } + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - if (mAction == ACTION_MANAGE) { - mDisplayState.acceptMimes = new String[] { "*/*" }; - mDisplayState.allowMultiple = true; - } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { - mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); + mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, + R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close); + + mDrawerLayout.setDrawerListener(mDrawerListener); + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + + if (icicle != null) { + mState = icicle.getParcelable(EXTRA_STATE); } else { - mDisplayState.acceptMimes = new String[] { intent.getType() }; + buildDefaultState(); } - mDisplayState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); - - setResult(Activity.RESULT_CANCELED); - setContentView(R.layout.activity); + if (mState.action == ACTION_MANAGE) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { final String mimeType = getIntent().getType(); final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); SaveFragment.show(getFragmentManager(), mimeType, title); } - if (mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_GET_CONTENT) { final Intent moreApps = new Intent(getIntent()); moreApps.setComponent(null); moreApps.setPackage(null); RootsFragment.show(getFragmentManager(), moreApps); - } else if (mAction == ACTION_OPEN || mAction == ACTION_CREATE) { + } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) { RootsFragment.show(getFragmentManager(), null); } - if (mAction == ACTION_MANAGE) { - mDisplayState.sortOrder = SORT_ORDER_LAST_MODIFIED; - } + onCurrentDirectoryChanged(); + } - mRootsContainer = findViewById(R.id.container_roots); + private void buildDefaultState() { + mState = new State(); - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + final Intent intent = getIntent(); + final String action = intent.getAction(); + if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { + mState.action = ACTION_OPEN; + } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { + mState.action = ACTION_CREATE; + } else if (Intent.ACTION_GET_CONTENT.equals(action)) { + mState.action = ACTION_GET_CONTENT; + } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) { + mState.action = ACTION_MANAGE; + } - mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, - R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close); + if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { + mState.allowMultiple = intent.getBooleanExtra( + Intent.EXTRA_ALLOW_MULTIPLE, false); + } - mDrawerLayout.setDrawerListener(mDrawerListener); - mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + if (mState.action == ACTION_MANAGE) { + mState.acceptMimes = new String[] { "*/*" }; + mState.allowMultiple = true; + } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { + mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); + } else { + mState.acceptMimes = new String[] { intent.getType() }; + } - if (mAction == ACTION_MANAGE) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); + mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this); + + if (mState.action == ACTION_MANAGE) { + mState.sortOrder = SORT_ORDER_LAST_MODIFIED; + } + if (mState.action == ACTION_MANAGE) { final Uri rootUri = intent.getData(); - final DocumentRoot root = mRoots.findRoot(rootUri); + final RootInfo root = mRoots.findRoot(rootUri); if (root != null) { onRootPicked(root, true); } else { @@ -169,8 +178,6 @@ public class DocumentsActivity extends Activity { } } else { - mDrawerLayout.openDrawer(mRootsContainer); - // Restore last stack for calling package // TODO: move into async loader final String packageName = getCallingPackage(); @@ -178,17 +185,17 @@ public class DocumentsActivity extends Activity { .query(RecentsProvider.buildResume(packageName), null, null, null, null); try { if (cursor.moveToFirst()) { - final String raw = cursor.getString( + final byte[] rawStack = cursor.getBlob( cursor.getColumnIndex(RecentsProvider.COL_PATH)); - mStack = DocumentStack.deserialize(getContentResolver(), raw); + DurableUtils.readFromArray(rawStack, mState.stack); } - } catch (FileNotFoundException e) { + } catch (IOException e) { Log.w(TAG, "Failed to resume", e); } finally { cursor.close(); } - onCurrentDirectoryChanged(); + mDrawerLayout.openDrawer(mRootsContainer); } } @@ -196,10 +203,10 @@ public class DocumentsActivity extends Activity { public void onStart() { super.onStart(); - if (mAction == ACTION_MANAGE) { - mDisplayState.showSize = true; + if (mState.action == ACTION_MANAGE) { + mState.showSize = true; } else { - mDisplayState.showSize = SettingsActivity.getDisplayFileSize(this); + mState.showSize = SettingsActivity.getDisplayFileSize(this); } } @@ -242,9 +249,9 @@ public class DocumentsActivity extends Activity { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); actionBar.setIcon(new ColorDrawable()); - if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { actionBar.setTitle(R.string.title_open); - } else if (mAction == ACTION_CREATE) { + } else if (mState.action == ACTION_CREATE) { actionBar.setTitle(R.string.title_save); } @@ -252,7 +259,7 @@ public class DocumentsActivity extends Activity { mDrawerToggle.setDrawerIndicatorEnabled(true); } else { - final DocumentRoot root = getCurrentRoot(); + final RootInfo root = getCurrentRoot(); actionBar.setIcon(root != null ? root.loadIcon(this) : null); if (mRoots.isRecentsRoot(root)) { @@ -262,13 +269,13 @@ public class DocumentsActivity extends Activity { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); actionBar.setTitle(null); actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener); - actionBar.setSelectedNavigationItem(mDisplayState.sortOrder); + actionBar.setSelectedNavigationItem(mState.sortOrder); } - if (mStack.size() > 1) { + if (mState.stack.size() > 1) { actionBar.setDisplayHomeAsUpEnabled(true); mDrawerToggle.setDrawerIndicatorEnabled(false); - } else if (mAction == ACTION_MANAGE) { + } else if (mState.action == ACTION_MANAGE) { actionBar.setDisplayHomeAsUpEnabled(false); mDrawerToggle.setDrawerIndicatorEnabled(false); } else { @@ -288,7 +295,7 @@ public class DocumentsActivity extends Activity { mSearchView.setOnQueryTextListener(new OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - mCurrentSearch = query; + mState.currentSearch = query; onCurrentDirectoryChanged(); mSearchView.setIconified(true); return true; @@ -303,7 +310,7 @@ public class DocumentsActivity extends Activity { mSearchView.setOnCloseListener(new OnCloseListener() { @Override public boolean onClose() { - mCurrentSearch = null; + mState.currentSearch = null; onCurrentDirectoryChanged(); return false; } @@ -317,7 +324,7 @@ public class DocumentsActivity extends Activity { super.onPrepareOptionsMenu(menu); final FragmentManager fm = getFragmentManager(); - final Document cwd = getCurrentDirectory(); + final DocumentInfo cwd = getCurrentDirectory(); final MenuItem createDir = menu.findItem(R.id.menu_create_dir); final MenuItem search = menu.findItem(R.id.menu_search); @@ -325,11 +332,11 @@ public class DocumentsActivity extends Activity { final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem settings = menu.findItem(R.id.menu_settings); - grid.setVisible(mDisplayState.mode != MODE_GRID); - list.setVisible(mDisplayState.mode != MODE_LIST); + grid.setVisible(mState.mode != MODE_GRID); + list.setVisible(mState.mode != MODE_LIST); final boolean searchVisible; - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { createDir.setVisible(cwd != null && cwd.isCreateSupported()); searchVisible = false; @@ -348,7 +355,7 @@ public class DocumentsActivity extends Activity { // TODO: close any search in-progress when hiding search.setVisible(searchVisible); - settings.setVisible(mAction != ACTION_MANAGE); + settings.setVisible(mState.action != ACTION_MANAGE); return true; } @@ -370,13 +377,13 @@ public class DocumentsActivity extends Activity { return false; } else if (id == R.id.menu_grid) { // TODO: persist explicit user mode for cwd - mDisplayState.mode = MODE_GRID; + mState.mode = MODE_GRID; updateDisplayState(); invalidateOptionsMenu(); return true; } else if (id == R.id.menu_list) { // TODO: persist explicit user mode for cwd - mDisplayState.mode = MODE_LIST; + mState.mode = MODE_LIST; updateDisplayState(); invalidateOptionsMenu(); return true; @@ -390,9 +397,9 @@ public class DocumentsActivity extends Activity { @Override public void onBackPressed() { - final int size = mStack.size(); + final int size = mState.stack.size(); if (size > 1) { - mStack.pop(); + mState.stack.pop(); onCurrentDirectoryChanged(); } else if (size == 1 && !mDrawerLayout.isDrawerOpen(mRootsContainer)) { // TODO: open root drawer once we can capture back key @@ -402,11 +409,23 @@ public class DocumentsActivity extends Activity { } } + @Override + protected void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + state.putParcelable(EXTRA_STATE, mState); + } + + @Override + protected void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + updateActionBar(); + } + // TODO: support additional sort orders private BaseAdapter mSortAdapter = new BaseAdapter() { @Override public int getCount() { - return mDisplayState.showSize ? 3 : 2; + return mState.showSize ? 3 : 2; } @Override @@ -438,8 +457,8 @@ public class DocumentsActivity extends Activity { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - if (mStack.size() > 0) { - title.setText(mStack.getTitle(mRoots)); + if (mState.stack.size() > 0) { + title.setText(mState.stack.getTitle(mRoots)); } else { // No directory means recents title.setText(R.string.root_recent); @@ -467,43 +486,43 @@ public class DocumentsActivity extends Activity { private OnNavigationListener mSortListener = new OnNavigationListener() { @Override public boolean onNavigationItemSelected(int itemPosition, long itemId) { - mDisplayState.sortOrder = itemPosition; + mState.sortOrder = itemPosition; updateDisplayState(); return true; } }; - public DocumentRoot getCurrentRoot() { - if (mStack.size() > 0) { - return mStack.getRoot(mRoots); + public RootInfo getCurrentRoot() { + if (mState.stack.size() > 0) { + return mState.stack.getRoot(mRoots); } else { return mRoots.getRecentsRoot(); } } - public Document getCurrentDirectory() { - return mStack.peek(); + public DocumentInfo getCurrentDirectory() { + return mState.stack.peek(); } - public DisplayState getDisplayState() { - return mDisplayState; + public State getDisplayState() { + return mState; } private void onCurrentDirectoryChanged() { final FragmentManager fm = getFragmentManager(); - final Document cwd = getCurrentDirectory(); + final DocumentInfo cwd = getCurrentDirectory(); if (cwd == null) { // No directory means recents - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { RecentsCreateFragment.show(fm); } else { DirectoryFragment.showRecentsOpen(fm); } } else { - if (mCurrentSearch != null) { + if (mState.currentSearch != null) { // Ongoing search - DirectoryFragment.showSearch(fm, cwd.uri, mCurrentSearch); + DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch); } else { // Normal boring directory DirectoryFragment.showNormal(fm, cwd.uri); @@ -511,7 +530,7 @@ public class DocumentsActivity extends Activity { } // Forget any replacement target - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { final SaveFragment save = SaveFragment.get(fm); if (save != null) { save.setReplaceTarget(null); @@ -529,18 +548,18 @@ public class DocumentsActivity extends Activity { } public void onStackPicked(DocumentStack stack) { - mStack = stack; + mState.stack = stack; onCurrentDirectoryChanged(); } - public void onRootPicked(DocumentRoot root, boolean closeDrawer) { + public void onRootPicked(RootInfo root, boolean closeDrawer) { // Clear entire backstack and start in new root - mStack.clear(); + mState.stack.clear(); if (!mRoots.isRecentsRoot(root)) { try { - final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId); - onDocumentPicked(Document.fromUri(getContentResolver(), uri)); + final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId); + onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri)); } catch (FileNotFoundException e) { } } else { @@ -561,24 +580,24 @@ public class DocumentsActivity extends Activity { finish(); } - public void onDocumentPicked(Document doc) { + public void onDocumentPicked(DocumentInfo doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { // TODO: query display mode user preference for this dir if (doc.isGridPreferred()) { - mDisplayState.mode = MODE_GRID; + mState.mode = MODE_GRID; } else { - mDisplayState.mode = MODE_LIST; + mState.mode = MODE_LIST; } - mStack.push(doc); + mState.stack.push(doc); onCurrentDirectoryChanged(); - } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Explicit file picked, return onFinished(doc.uri); - } else if (mAction == ACTION_CREATE) { + } else if (mState.action == ACTION_CREATE) { // Replace selected file SaveFragment.get(fm).setReplaceTarget(doc); - } else if (mAction == ACTION_MANAGE) { + } else if (mState.action == ACTION_MANAGE) { // Open the document final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -591,8 +610,8 @@ public class DocumentsActivity extends Activity { } } - public void onDocumentsPicked(List<Document> docs) { - if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + public void onDocumentsPicked(List<DocumentInfo> docs) { + if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { final int size = docs.size(); final Uri[] uris = new Uri[size]; for (int i = 0; i < size; i++) { @@ -602,21 +621,19 @@ public class DocumentsActivity extends Activity { } } - public void onSaveRequested(Document replaceTarget) { + public void onSaveRequested(DocumentInfo replaceTarget) { onFinished(replaceTarget.uri); } public void onSaveRequested(String mimeType, String displayName) { - final Document cwd = getCurrentDirectory(); + final DocumentInfo cwd = getCurrentDirectory(); final String authority = cwd.uri.getAuthority(); final ContentProviderClient client = getContentResolver() .acquireUnstableContentProviderClient(authority); try { - final String docId = DocumentsContract.createDocument(client, - DocumentsContract.getDocId(cwd.uri), mimeType, displayName); - - final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId); + final Uri childUri = DocumentsContract.createDocument( + getContentResolver(), cwd.uri, mimeType, displayName); onFinished(childUri); } catch (Exception e) { Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); @@ -631,14 +648,14 @@ public class DocumentsActivity extends Activity { final ContentResolver resolver = getContentResolver(); final ContentValues values = new ContentValues(); - final String rawStack = DocumentStack.serialize(mStack); - if (mAction == ACTION_CREATE) { + final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack); + if (mState.action == ACTION_CREATE) { // Remember stack for last create values.clear(); values.put(RecentsProvider.COL_PATH, rawStack); resolver.insert(RecentsProvider.buildRecentCreate(), values); - } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Remember opened items for (Uri uri : uris) { values.clear(); @@ -658,14 +675,14 @@ public class DocumentsActivity extends Activity { intent.setData(uris[0]); } else if (uris.length > 1) { final ClipData clipData = new ClipData( - null, mDisplayState.acceptMimes, new ClipData.Item(uris[0])); + null, mState.acceptMimes, new ClipData.Item(uris[0])); for (int i = 1; i < uris.length; i++) { clipData.addItem(new ClipData.Item(uris[i])); } intent.setClipData(clipData); } - if (mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_GET_CONTENT) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -677,7 +694,7 @@ public class DocumentsActivity extends Activity { finish(); } - public static class DisplayState { + public static class State implements android.os.Parcelable { public int action; public int mode = MODE_LIST; public String[] acceptMimes; @@ -685,6 +702,12 @@ public class DocumentsActivity extends Activity { public boolean allowMultiple = false; public boolean showSize = false; public boolean localOnly = false; + public boolean showAdvanced = false; + + /** Current user navigation stack; empty implies recents. */ + public DocumentStack stack = new DocumentStack(); + /** Currently active search, overriding any stack. */ + public String currentSearch; public static final int ACTION_OPEN = 1; public static final int ACTION_CREATE = 2; @@ -697,11 +720,53 @@ public class DocumentsActivity extends Activity { public static final int SORT_ORDER_DISPLAY_NAME = 0; public static final int SORT_ORDER_LAST_MODIFIED = 1; public static final int SORT_ORDER_SIZE = 2; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(action); + out.writeInt(mode); + out.writeStringArray(acceptMimes); + out.writeInt(sortOrder); + out.writeInt(allowMultiple ? 1 : 0); + out.writeInt(showSize ? 1 : 0); + out.writeInt(localOnly ? 1 : 0); + out.writeInt(showAdvanced ? 1 : 0); + DurableUtils.writeToParcel(out, stack); + out.writeString(currentSearch); + } + + public static final Creator<State> CREATOR = new Creator<State>() { + @Override + public State createFromParcel(Parcel in) { + final State state = new State(); + state.action = in.readInt(); + state.mode = in.readInt(); + state.acceptMimes = in.readStringArray(); + state.sortOrder = in.readInt(); + state.allowMultiple = in.readInt() != 0; + state.showSize = in.readInt() != 0; + state.localOnly = in.readInt() != 0; + state.showAdvanced = in.readInt() != 0; + DurableUtils.readFromParcel(in, state.stack); + state.currentSearch = in.readString(); + return state; + } + + @Override + public State[] newArray(int size) { + return new State[size]; + } + }; } private void dumpStack() { Log.d(TAG, "Current stack:"); - for (Document doc : mStack) { + for (DocumentInfo doc : mState.stack) { Log.d(TAG, "--> " + doc); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index a9929de..15ad061 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -16,10 +16,10 @@ package com.android.documentsui; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; import com.android.internal.util.Predicate; -public class MimePredicate implements Predicate<Document> { +public class MimePredicate implements Predicate<DocumentInfo> { private final String[] mFilters; public MimePredicate(String[] filters) { @@ -27,7 +27,7 @@ public class MimePredicate implements Predicate<Document> { } @Override - public boolean apply(Document doc) { + public boolean apply(DocumentInfo doc) { if (doc.isDirectory()) { return true; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java new file mode 100644 index 0000000..756a297 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import static com.android.documentsui.DocumentsActivity.TAG; + +import android.content.AsyncTaskLoader; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.database.MergeCursor; +import android.net.Uri; +import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Root; +import android.util.Log; + +import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.model.RootInfo; +import com.google.android.collect.Maps; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.AbstractFuture; + +import libcore.io.IoUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { + + public static final int MAX_OUTSTANDING_RECENTS = 2; + + /** + * Time to wait for first pass to complete before returning partial results. + */ + public static final int MAX_FIRST_PASS_WAIT_MILLIS = 500; + + /** + * Maximum documents from a single root. + */ + public static final int MAX_DOCS_FROM_ROOT = 24; + + private static final ExecutorService sExecutor = buildExecutor(); + + /** + * Create a bounded thread pool for fetching recents; it creates threads as + * needed (up to maximum) and reclaims them when finished. + */ + private static ExecutorService buildExecutor() { + // Create a bounded thread pool for fetching recents; it creates + // threads as needed (up to maximum) and reclaims them when finished. + final ThreadPoolExecutor executor = new ThreadPoolExecutor( + MAX_OUTSTANDING_RECENTS, MAX_OUTSTANDING_RECENTS, 10, TimeUnit.SECONDS, + new LinkedBlockingQueue<Runnable>()); + executor.allowCoreThreadTimeOut(true); + return executor; + } + + private final List<RootInfo> mRoots; + + private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap(); + + private final int mSortOrder = State.SORT_ORDER_LAST_MODIFIED; + + private CountDownLatch mFirstPassLatch; + private volatile boolean mFirstPassDone; + + private DirectoryResult mResult; + + // TODO: create better transfer of ownership around cursor to ensure its + // closed in all edge cases. + + public class RecentTask extends AbstractFuture<Cursor> implements Runnable, Closeable { + public final String authority; + public final String rootId; + + private Cursor mWithRoot; + + public RecentTask(String authority, String rootId) { + this.authority = authority; + this.rootId = rootId; + } + + @Override + public void run() { + if (isCancelled()) return; + + final ContentResolver resolver = getContext().getContentResolver(); + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + authority); + try { + final Uri uri = DocumentsContract.buildRecentDocumentsUri(authority, rootId); + final Cursor cursor = client.query( + uri, null, null, null, DirectoryLoader.getQuerySortOrder(mSortOrder)); + mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT); + set(mWithRoot); + + mFirstPassLatch.countDown(); + if (mFirstPassDone) { + onContentChanged(); + } + + } catch (Exception e) { + setException(e); + } finally { + ContentProviderClient.closeQuietly(client); + } + } + + @Override + public void close() throws IOException { + IoUtils.closeQuietly(mWithRoot); + } + } + + public RecentLoader(Context context, List<RootInfo> roots) { + super(context); + mRoots = roots; + } + + @Override + public DirectoryResult loadInBackground() { + if (mFirstPassLatch == null) { + // First time through we kick off all the recent tasks, and wait + // around to see if everyone finishes quickly. + + for (RootInfo root : mRoots) { + if ((root.flags & Root.FLAG_SUPPORTS_RECENTS) != 0) { + final RecentTask task = new RecentTask(root.authority, root.rootId); + mTasks.put(root, task); + } + } + + mFirstPassLatch = new CountDownLatch(mTasks.size()); + for (RecentTask task : mTasks.values()) { + sExecutor.execute(task); + } + + try { + mFirstPassLatch.await(MAX_FIRST_PASS_WAIT_MILLIS, TimeUnit.MILLISECONDS); + mFirstPassDone = true; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + // Collect all finished tasks + List<Cursor> cursors = Lists.newArrayList(); + for (RecentTask task : mTasks.values()) { + if (task.isDone()) { + try { + cursors.add(task.get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + Log.w(TAG, "Failed to load " + task.authority + ", " + task.rootId, e); + } + } + } + + final DirectoryResult result = new DirectoryResult(); + if (cursors.size() > 0) { + final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()])); + final SortingCursorWrapper sorted = new SortingCursorWrapper( + merged, State.SORT_ORDER_LAST_MODIFIED) { + @Override + public void close() { + // Ignored, since we manage cursor lifecycle internally + } + }; + result.cursor = sorted; + } + return result; + } + + @Override + public void cancelLoadInBackground() { + super.cancelLoadInBackground(); + } + + @Override + public void deliverResult(DirectoryResult result) { + if (isReset()) { + IoUtils.closeQuietly(result); + return; + } + DirectoryResult oldResult = mResult; + mResult = result; + + if (isStarted()) { + super.deliverResult(result); + } + + if (oldResult != null && oldResult != result) { + IoUtils.closeQuietly(oldResult); + } + } + + @Override + protected void onStartLoading() { + if (mResult != null) { + deliverResult(mResult); + } + if (takeContentChanged() || mResult == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(DirectoryResult result) { + IoUtils.closeQuietly(result); + } + + @Override + protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped + onStopLoading(); + + for (RecentTask task : mTasks.values()) { + IoUtils.closeQuietly(task); + } + + IoUtils.closeQuietly(mResult); + mResult = null; + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 3447a51..fd7293d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -29,7 +29,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; -import android.provider.DocumentsContract.DocumentRoot; import android.text.TextUtils.TruncateAt; import android.util.Log; import android.view.LayoutInflater; @@ -43,11 +42,14 @@ import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.RootInfo; import com.google.android.collect.Lists; import libcore.io.IoUtils; -import java.io.FileNotFoundException; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -138,12 +140,13 @@ public class RecentsCreateFragment extends Fragment { uri, null, null, null, RecentsProvider.COL_TIMESTAMP + " DESC", signal); try { while (cursor != null && cursor.moveToNext()) { - final String rawStack = cursor.getString( + final byte[] raw = cursor.getBlob( cursor.getColumnIndex(RecentsProvider.COL_PATH)); try { - final DocumentStack stack = DocumentStack.deserialize(resolver, rawStack); + final DocumentStack stack = new DocumentStack(); + stack.read(new DataInputStream(new ByteArrayInputStream(raw))); result.add(stack); - } catch (FileNotFoundException e) { + } catch (IOException e) { Log.w(TAG, "Failed to resolve stack: " + e); } } @@ -181,7 +184,7 @@ public class RecentsCreateFragment extends Fragment { final View summaryList = convertView.findViewById(R.id.summary_list); final DocumentStack stack = getItem(position); - final DocumentRoot root = stack.getRoot(roots); + final RootInfo root = stack.getRoot(roots); icon.setImageDrawable(root.loadIcon(context)); final StringBuilder builder = new StringBuilder(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index dbcb039..0c87783 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -61,6 +61,7 @@ public class RecentsProvider extends ContentProvider { public static final String COL_PACKAGE_NAME = "package_name"; public static final String COL_TIMESTAMP = "timestamp"; + @Deprecated public static Uri buildRecentOpen() { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(AUTHORITY).appendPath("recent_open").build(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java new file mode 100644 index 0000000..d0e5ff6 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import android.database.AbstractCursor; +import android.database.Cursor; + +/** + * Cursor wrapper that adds columns to identify which root a document came from. + */ +public class RootCursorWrapper extends AbstractCursor { + private final String mAuthority; + private final String mRootId; + + private final Cursor mCursor; + private final int mCount; + + private final String[] mColumnNames; + + private final int mAuthorityIndex; + private final int mRootIdIndex; + + public static final String COLUMN_AUTHORITY = "android:authority"; + public static final String COLUMN_ROOT_ID = "android:rootId"; + + public RootCursorWrapper(String authority, String rootId, Cursor cursor, int maxCount) { + mAuthority = authority; + mRootId = rootId; + mCursor = cursor; + + final int count = cursor.getCount(); + if (maxCount > 0 && count > maxCount) { + mCount = maxCount; + } else { + mCount = count; + } + + if (cursor.getColumnIndex(COLUMN_AUTHORITY) != -1 + || cursor.getColumnIndex(COLUMN_ROOT_ID) != -1) { + throw new IllegalArgumentException("Cursor contains internal columns!"); + } + final String[] before = cursor.getColumnNames(); + mColumnNames = new String[before.length + 2]; + System.arraycopy(before, 0, mColumnNames, 0, before.length); + mAuthorityIndex = before.length; + mRootIdIndex = before.length + 1; + mColumnNames[mAuthorityIndex] = COLUMN_AUTHORITY; + mColumnNames[mRootIdIndex] = COLUMN_ROOT_ID; + } + + @Override + public void close() { + super.close(); + mCursor.close(); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + return mCursor.moveToPosition(newPosition); + } + + @Override + public String[] getColumnNames() { + return mColumnNames; + } + + @Override + public int getCount() { + return mCount; + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public String getString(int column) { + if (column == mAuthorityIndex) { + return mAuthority; + } else if (column == mRootIdIndex) { + return mRootId; + } else { + return mCursor.getString(column); + } + } + + @Override + public int getType(int column) { + return mCursor.getType(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index aa21457..0b10f19 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -25,17 +25,23 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentRoot; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; import android.util.Log; +import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.model.RootInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Objects; import com.google.android.collect.Lists; +import libcore.io.IoUtils; + +import java.util.ArrayList; import java.util.List; /** @@ -46,11 +52,13 @@ public class RootsCache { // TODO: cache roots in local provider to avoid spinning up backends // TODO: root updates should trigger UI refresh + private static final boolean RECENTS_ENABLED = true; + private final Context mContext; - public List<DocumentRoot> mRoots = Lists.newArrayList(); + public List<RootInfo> mRoots = Lists.newArrayList(); - private DocumentRoot mRecentsRoot; + private RootInfo mRecentsRoot; public RootsCache(Context context) { mContext = context; @@ -64,14 +72,13 @@ public class RootsCache { public void update() { mRoots.clear(); - { + if (RECENTS_ENABLED) { // Create special root for recents - final DocumentRoot root = new DocumentRoot(); - root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT; - root.docId = null; + final RootInfo root = new RootInfo(); + root.rootType = Root.ROOT_TYPE_SHORTCUT; root.icon = R.drawable.ic_dir; + root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE; root.title = mContext.getString(R.string.root_recent); - root.summary = null; root.availableBytes = -1; mRoots.add(root); @@ -89,28 +96,32 @@ public class RootsCache { // TODO: remove deprecated customRoots flag // TODO: populate roots on background thread, and cache results + final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority); final ContentProviderClient client = resolver .acquireUnstableContentProviderClient(info.authority); + Cursor cursor = null; try { - final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client); - for (DocumentRoot root : roots) { - root.authority = info.authority; + cursor = client.query(rootsUri, null, null, null, null); + while (cursor.moveToNext()) { + final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor); + mRoots.add(root); } - mRoots.addAll(roots); } catch (Exception e) { Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e); } finally { + IoUtils.closeQuietly(cursor); ContentProviderClient.closeQuietly(client); } } } } - public DocumentRoot findRoot(Uri uri) { + @Deprecated + public RootInfo findRoot(Uri uri) { final String authority = uri.getAuthority(); - final String docId = DocumentsContract.getDocId(uri); - for (DocumentRoot root : mRoots) { - if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) { + final String docId = DocumentsContract.getDocumentId(uri); + for (RootInfo root : mRoots) { + if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) { return root; } } @@ -118,23 +129,87 @@ public class RootsCache { } @GuardedBy("ActivityThread") - public DocumentRoot getRecentsRoot() { + public RootInfo getRoot(String authority, String rootId) { + for (RootInfo root : mRoots) { + if (Objects.equal(root.authority, authority) && Objects.equal(root.rootId, rootId)) { + return root; + } + } + return null; + } + + @GuardedBy("ActivityThread") + public RootInfo getRecentsRoot() { return mRecentsRoot; } @GuardedBy("ActivityThread") - public boolean isRecentsRoot(DocumentRoot root) { + public boolean isRecentsRoot(RootInfo root) { return mRecentsRoot == root; } @GuardedBy("ActivityThread") - public List<DocumentRoot> getRoots() { + public List<RootInfo> getRoots() { return mRoots; } + /** + * Flags that declare explicit content types. + */ + private static final int FLAGS_CONTENT_MASK = Root.FLAG_PROVIDES_IMAGES + | Root.FLAG_PROVIDES_AUDIO | Root.FLAG_PROVIDES_VIDEO; + + @GuardedBy("ActivityThread") + public List<RootInfo> getMatchingRoots(State state) { + + // Determine acceptable content flags + int includeFlags = 0; + for (String acceptMime : state.acceptMimes) { + final String[] type = acceptMime.split("/"); + if (type.length != 2) continue; + + if ("image".equals(type[0])) { + includeFlags |= Root.FLAG_PROVIDES_IMAGES; + } else if ("audio".equals(type[0])) { + includeFlags |= Root.FLAG_PROVIDES_AUDIO; + } else if ("video".equals(type[0])) { + includeFlags |= Root.FLAG_PROVIDES_VIDEO; + } else if ("*".equals(type[0])) { + includeFlags |= Root.FLAG_PROVIDES_IMAGES | Root.FLAG_PROVIDES_AUDIO + | Root.FLAG_PROVIDES_VIDEO; + } + } + + ArrayList<RootInfo> matching = Lists.newArrayList(); + for (RootInfo root : mRoots) { + final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0; + final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0; + final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0; + + // Exclude read-only devices when creating + if (state.action == State.ACTION_CREATE && !supportsCreate) continue; + // Exclude advanced devices when not requested + if (!state.showAdvanced && advanced) continue; + // Exclude non-local devices when local only + if (state.localOnly && !localOnly) continue; + + if ((root.flags & FLAGS_CONTENT_MASK) != 0) { + // This root offers specific content, so only include if the + // caller asked for that content type. + if ((root.flags & includeFlags) == 0) { + // Sorry, no overlap. + continue; + } + } + + matching.add(root); + } + return matching; + } + @GuardedBy("ActivityThread") public static Drawable resolveDocumentIcon(Context context, String mimeType) { - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (Document.MIME_TYPE_DIR.equals(mimeType)) { return context.getResources().getDrawable(R.drawable.ic_dir); } else { final PackageManager pm = context.getPackageManager(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 2cfa841..ef3a31d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -16,8 +16,6 @@ package com.android.documentsui; -import static com.android.documentsui.DocumentsActivity.TAG; - import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -26,9 +24,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.provider.DocumentsContract.DocumentRoot; +import android.provider.DocumentsContract.Root; import android.text.format.Formatter; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,8 +36,10 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import com.android.documentsui.DocumentsActivity.State; import com.android.documentsui.SectionedListAdapter.SectionAdapter; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; +import com.android.documentsui.model.RootInfo; import java.util.Comparator; import java.util.List; @@ -75,24 +74,31 @@ public class RootsFragment extends Fragment { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Context context = inflater.getContext(); - final RootsCache roots = DocumentsApplication.getRootsCache(context); final View view = inflater.inflate(R.layout.fragment_roots, container, false); mList = (ListView) view.findViewById(android.R.id.list); mList.setOnItemClickListener(mItemListener); - final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); - mAdapter = new SectionedRootsAdapter(context, roots.getRoots(), includeApps); - return view; } @Override public void onStart() { super.onStart(); + updateRootsAdapter(); + } + private void updateRootsAdapter() { final Context context = getActivity(); - mAdapter.updateVisible(SettingsActivity.getDisplayAdvancedDevices(context)); + + final State state = ((DocumentsActivity) context).getDisplayState(); + state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context); + + final RootsCache roots = DocumentsApplication.getRootsCache(context); + final List<RootInfo> matchingRoots = roots.getMatchingRoots(state); + final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); + + mAdapter = new SectionedRootsAdapter(context, matchingRoots, includeApps); mList.setAdapter(mAdapter); } @@ -101,8 +107,8 @@ public class RootsFragment extends Fragment { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); final Object item = mAdapter.getItem(position); - if (item instanceof DocumentRoot) { - activity.onRootPicked((DocumentRoot) item, true); + if (item instanceof RootInfo) { + activity.onRootPicked((RootInfo) item, true); } else if (item instanceof ResolveInfo) { activity.onAppPicked((ResolveInfo) item); } else { @@ -111,7 +117,7 @@ public class RootsFragment extends Fragment { } }; - private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter { + private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter { private int mHeaderId; public RootsAdapter(Context context, int headerId) { @@ -131,15 +137,13 @@ public class RootsFragment extends Fragment { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - final DocumentRoot root = getItem(position); + final RootInfo root = getItem(position); icon.setImageDrawable(root.loadIcon(context)); title.setText(root.title); // Device summary is always available space final String summaryText; - if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE - || root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED) - && root.availableBytes >= 0) { + if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) { summaryText = context.getString(R.string.root_available_bytes, Formatter.formatFileSize(context, root.availableBytes)); } else { @@ -212,31 +216,24 @@ public class RootsFragment extends Fragment { private final RootsAdapter mServices; private final RootsAdapter mShortcuts; private final RootsAdapter mDevices; - private final RootsAdapter mDevicesAdvanced; private final AppsAdapter mApps; - public SectionedRootsAdapter(Context context, List<DocumentRoot> roots, Intent includeApps) { + public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) { mServices = new RootsAdapter(context, R.string.root_type_service); mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut); mDevices = new RootsAdapter(context, R.string.root_type_device); - mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device); mApps = new AppsAdapter(context); - for (DocumentRoot root : roots) { - Log.d(TAG, "Found rootType=" + root.rootType); + for (RootInfo root : roots) { switch (root.rootType) { - case DocumentRoot.ROOT_TYPE_SERVICE: + case Root.ROOT_TYPE_SERVICE: mServices.add(root); break; - case DocumentRoot.ROOT_TYPE_SHORTCUT: + case Root.ROOT_TYPE_SHORTCUT: mShortcuts.add(root); break; - case DocumentRoot.ROOT_TYPE_DEVICE: + case Root.ROOT_TYPE_DEVICE: mDevices.add(root); - mDevicesAdvanced.add(root); - break; - case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED: - mDevicesAdvanced.add(root); break; } } @@ -258,37 +255,36 @@ public class RootsFragment extends Fragment { mServices.sort(comp); mShortcuts.sort(comp); mDevices.sort(comp); - mDevicesAdvanced.sort(comp); - } - public void updateVisible(boolean showAdvanced) { - clearSections(); if (mServices.getCount() > 0) { addSection(mServices); } if (mShortcuts.getCount() > 0) { addSection(mShortcuts); } - - final RootsAdapter devices = showAdvanced ? mDevicesAdvanced : mDevices; - if (devices.getCount() > 0) { - addSection(devices); + if (mDevices.getCount() > 0) { + addSection(mDevices); } - if (mApps.getCount() > 0) { addSection(mApps); } } } - public static class RootComparator implements Comparator<DocumentRoot> { + public static class RootComparator implements Comparator<RootInfo> { @Override - public int compare(DocumentRoot lhs, DocumentRoot rhs) { - final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title); + public int compare(RootInfo lhs, RootInfo rhs) { + if (lhs.authority == null) { + return -1; + } else if (rhs.authority == null) { + return 1; + } + + final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title); if (score != 0) { return score; } else { - return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary); + return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index 7e1a297..8b0a974 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -31,7 +31,7 @@ import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; /** * Display document title editor and save button. @@ -39,7 +39,7 @@ import com.android.documentsui.model.Document; public class SaveFragment extends Fragment { public static final String TAG = "SaveFragment"; - private Document mReplaceTarget; + private DocumentInfo mReplaceTarget; private EditText mDisplayName; private Button mSave; private boolean mIgnoreNextEdit; @@ -128,7 +128,7 @@ public class SaveFragment extends Fragment { * without changing the filename. Can be set to {@code null} if user * navigates outside the target directory. */ - public void setReplaceTarget(Document replaceTarget) { + public void setReplaceTarget(DocumentInfo replaceTarget) { mReplaceTarget = replaceTarget; if (mReplaceTarget != null) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java new file mode 100644 index 0000000..b434a35 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; + +import android.database.AbstractCursor; +import android.database.Cursor; +import android.provider.DocumentsContract.Document; + +/** + * Cursor wrapper that presents a sorted view of the underlying cursor. Handles + * common {@link Document} sorting modes, such as ordering directories first. + */ +public class SortingCursorWrapper extends AbstractCursor { + private final Cursor mCursor; + + private final int[] mPosition; + private final String[] mValueString; + private final long[] mValueLong; + + public SortingCursorWrapper(Cursor cursor, int sortOrder) { + mCursor = cursor; + + final int count = cursor.getCount(); + mPosition = new int[count]; + switch (sortOrder) { + case SORT_ORDER_DISPLAY_NAME: + mValueString = new String[count]; + mValueLong = null; + break; + case SORT_ORDER_LAST_MODIFIED: + case SORT_ORDER_SIZE: + mValueString = null; + mValueLong = new long[count]; + break; + default: + throw new IllegalArgumentException(); + } + + cursor.moveToPosition(-1); + for (int i = 0; i < count; i++) { + cursor.moveToNext(); + mPosition[i] = i; + + switch (sortOrder) { + case SORT_ORDER_DISPLAY_NAME: + final String mimeType = cursor.getString( + cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); + final String displayName = cursor.getString( + cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)); + if (Document.MIME_TYPE_DIR.equals(mimeType)) { + mValueString[i] = '\001' + displayName; + } else { + mValueString[i] = displayName; + } + break; + case SORT_ORDER_LAST_MODIFIED: + mValueLong[i] = cursor.getLong( + cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED)); + break; + case SORT_ORDER_SIZE: + mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_SIZE)); + break; + } + } + + switch (sortOrder) { + case SORT_ORDER_DISPLAY_NAME: + synchronized (SortingCursorWrapper.class) { + + binarySort(mPosition, mValueString); + } + break; + case SORT_ORDER_LAST_MODIFIED: + case SORT_ORDER_SIZE: + binarySort(mPosition, mValueLong); + break; + } + } + + @Override + public void close() { + super.close(); + mCursor.close(); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + return mCursor.moveToPosition(mPosition[newPosition]); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public int getCount() { + return mCursor.getCount(); + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public int getType(int column) { + return mCursor.getType(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + + /** + * Borrowed from TimSort.binarySort(), but modified to sort two column + * dataset. + */ + private static void binarySort(int[] position, String[] value) { + final int count = position.length; + for (int start = 1; start < count; start++) { + final int pivotPosition = position[start]; + final String pivotValue = value[start]; + + int left = 0; + int right = start; + + while (left < right) { + int mid = (left + right) >>> 1; + + final String lhs = pivotValue; + final String rhs = value[mid]; + final int compare; + if (lhs == null) { + compare = -1; + } else if (rhs == null) { + compare = 1; + } else { + compare = lhs.compareToIgnoreCase(rhs); + } + + if (compare < 0) { + right = mid; + } else { + left = mid + 1; + } + } + + int n = start - left; + switch (n) { + case 2: + position[left + 2] = position[left + 1]; + value[left + 2] = value[left + 1]; + case 1: + position[left + 1] = position[left]; + value[left + 1] = value[left]; + break; + default: + System.arraycopy(position, left, position, left + 1, n); + System.arraycopy(value, left, value, left + 1, n); + } + + position[left] = pivotPosition; + value[left] = pivotValue; + } + } + + /** + * Borrowed from TimSort.binarySort(), but modified to sort two column + * dataset. + */ + private static void binarySort(int[] position, long[] value) { + final int count = position.length; + for (int start = 1; start < count; start++) { + final int pivotPosition = position[start]; + final long pivotValue = value[start]; + + int left = 0; + int right = start; + + while (left < right) { + int mid = (left + right) >>> 1; + + final long lhs = pivotValue; + final long rhs = value[mid]; + final int compare = Long.compare(lhs, rhs); + if (compare > 0) { + right = mid; + } else { + left = mid + 1; + } + } + + int n = start - left; + switch (n) { + case 2: + position[left + 2] = position[left + 1]; + value[left + 2] = value[left + 1]; + case 1: + position[left + 1] = position[left]; + value[left + 1] = value[left]; + break; + default: + System.arraycopy(position, left, position, left + 1, n); + System.arraycopy(value, left, value, left + 1, n); + } + + position[left] = pivotPosition; + value[left] = pivotValue; + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java index f6548e8..2405cb5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java @@ -32,7 +32,6 @@ import android.widget.TextView; import libcore.io.IoUtils; import libcore.io.Streams; -import java.io.IOException; import java.io.InputStream; public class TestActivity extends Activity { @@ -50,8 +49,11 @@ public class TestActivity extends Activity { view.setOrientation(LinearLayout.VERTICAL); final CheckBox multiple = new CheckBox(context); - multiple.setText("ALLOW_MULTIPLE"); + multiple.setText("\nALLOW_MULTIPLE\n"); view.addView(multiple); + final CheckBox localOnly = new CheckBox(context); + localOnly.setText("\nLOCAL_ONLY\n"); + view.addView(localOnly); Button button; button = new Button(context); @@ -65,6 +67,9 @@ public class TestActivity extends Activity { if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } + if (localOnly.isChecked()) { + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + } startActivityForResult(intent, 42); } }); @@ -81,6 +86,28 @@ public class TestActivity extends Activity { if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } + if (localOnly.isChecked()) { + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + } + startActivityForResult(intent, 42); + } + }); + view.addView(button); + + button = new Button(context); + button.setText("OPEN_DOC audio/ogg"); + button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("audio/ogg"); + if (multiple.isChecked()) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + if (localOnly.isChecked()) { + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + } startActivityForResult(intent, 42); } }); @@ -99,6 +126,9 @@ public class TestActivity extends Activity { if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } + if (localOnly.isChecked()) { + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + } startActivityForResult(intent, 42); } }); @@ -113,6 +143,9 @@ public class TestActivity extends Activity { intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt"); + if (localOnly.isChecked()) { + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + } startActivityForResult(intent, 42); } }); @@ -129,6 +162,9 @@ public class TestActivity extends Activity { if (multiple.isChecked()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } + if (localOnly.isChecked()) { + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + } startActivityForResult(Intent.createChooser(intent, "Kittens!"), 42); } }); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java deleted file mode 100644 index 692d171..0000000 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.documentsui.model; - -import android.content.ContentResolver; -import android.database.Cursor; -import android.net.Uri; -import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.Documents; - -import com.android.documentsui.RecentsProvider; - -import libcore.io.IoUtils; - -import java.io.FileNotFoundException; -import java.util.Comparator; - -/** - * Representation of a single document. - */ -public class Document { - public final Uri uri; - public final String mimeType; - public final String displayName; - public final long lastModified; - public final int flags; - public final String summary; - public final long size; - - private Document(Uri uri, String mimeType, String displayName, long lastModified, int flags, - String summary, long size) { - this.uri = uri; - this.mimeType = mimeType; - this.displayName = displayName; - this.lastModified = lastModified; - this.flags = flags; - this.summary = summary; - this.size = size; - } - - public static Document fromDirectoryCursor(Uri parent, Cursor cursor) { - final String authority = parent.getAuthority(); - final String docId = getCursorString(cursor, DocumentColumns.DOC_ID); - - final Uri uri = DocumentsContract.buildDocumentUri(authority, docId); - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); - final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); - final long size = getCursorLong(cursor, DocumentColumns.SIZE); - - return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); - } - - @Deprecated - public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) - throws FileNotFoundException { - final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI)); - final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP); - - Cursor cursor = null; - try { - cursor = resolver.query(uri, null, null, null, null); - if (!cursor.moveToFirst()) { - throw new FileNotFoundException("Missing details for " + uri); - } - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS) - & Documents.FLAG_SUPPORTS_THUMBNAIL; - final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); - final long size = getCursorLong(cursor, DocumentColumns.SIZE); - - return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); - } catch (Throwable t) { - throw asFileNotFoundException(t); - } finally { - IoUtils.closeQuietly(cursor); - } - } - - public static Document fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException { - Cursor cursor = null; - try { - cursor = resolver.query(uri, null, null, null, null); - if (!cursor.moveToFirst()) { - throw new FileNotFoundException("Missing details for " + uri); - } - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); - final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); - final long size = getCursorLong(cursor, DocumentColumns.SIZE); - - return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); - } catch (Throwable t) { - throw asFileNotFoundException(t); - } finally { - IoUtils.closeQuietly(cursor); - } - } - - @Override - public String toString() { - return "Document{name=" + displayName + ", uri=" + uri + "}"; - } - - public boolean isCreateSupported() { - return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0; - } - - public boolean isSearchSupported() { - return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0; - } - - public boolean isThumbnailSupported() { - return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0; - } - - public boolean isDirectory() { - return Documents.MIME_TYPE_DIR.equals(mimeType); - } - - public boolean isGridPreferred() { - return (flags & Documents.FLAG_PREFERS_GRID) != 0; - } - - public boolean isDeleteSupported() { - return (flags & Documents.FLAG_SUPPORTS_DELETE) != 0; - } - - private static String getCursorString(Cursor cursor, String columnName) { - final int index = cursor.getColumnIndex(columnName); - return (index != -1) ? cursor.getString(index) : null; - } - - /** - * Missing or null values are returned as -1. - */ - private static long getCursorLong(Cursor cursor, String columnName) { - final int index = cursor.getColumnIndex(columnName); - if (index == -1) return -1; - final String value = cursor.getString(index); - if (value == null) return -1; - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - return -1; - } - } - - private static int getCursorInt(Cursor cursor, String columnName) { - final int index = cursor.getColumnIndex(columnName); - return (index != -1) ? cursor.getInt(index) : 0; - } - - public static class DisplayNameComparator implements Comparator<Document> { - @Override - public int compare(Document lhs, Document rhs) { - final boolean leftDir = lhs.isDirectory(); - final boolean rightDir = rhs.isDirectory(); - - if (leftDir != rightDir) { - return leftDir ? -1 : 1; - } else { - return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName); - } - } - } - - public static class LastModifiedComparator implements Comparator<Document> { - @Override - public int compare(Document lhs, Document rhs) { - return Long.compare(rhs.lastModified, lhs.lastModified); - } - } - - public static class SizeComparator implements Comparator<Document> { - @Override - public int compare(Document lhs, Document rhs) { - return Long.compare(rhs.size, lhs.size); - } - } - - public static FileNotFoundException asFileNotFoundException(Throwable t) - throws FileNotFoundException { - if (t instanceof FileNotFoundException) { - throw (FileNotFoundException) t; - } - final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage()); - fnfe.initCause(t); - throw fnfe; - } - - public static int compareToIgnoreCaseNullable(String lhs, String rhs) { - if (lhs == null) return -1; - if (rhs == null) return 1; - return lhs.compareToIgnoreCase(rhs); - } -} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java new file mode 100644 index 0000000..7721bcc --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui.model; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Document; + +import com.android.documentsui.RecentsProvider; +import com.android.documentsui.RootCursorWrapper; + +import libcore.io.IoUtils; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.ProtocolException; +import java.util.Comparator; + +/** + * Representation of a {@link Document}. + */ +public class DocumentInfo implements Durable { + private static final int VERSION_INIT = 1; + + public Uri uri; + public String mimeType; + public String displayName; + public long lastModified; + public int flags; + public String summary; + public long size; + public int icon; + + public DocumentInfo() { + reset(); + } + + @Override + public void reset() { + uri = null; + mimeType = null; + displayName = null; + lastModified = -1; + flags = 0; + summary = null; + size = -1; + icon = 0; + } + + @Override + public void read(DataInputStream in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_INIT: + final String rawUri = DurableUtils.readNullableString(in); + uri = rawUri != null ? Uri.parse(rawUri) : null; + mimeType = DurableUtils.readNullableString(in); + displayName = DurableUtils.readNullableString(in); + lastModified = in.readLong(); + flags = in.readInt(); + summary = DurableUtils.readNullableString(in); + size = in.readLong(); + icon = in.readInt(); + break; + default: + throw new ProtocolException("Unknown version " + version); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(VERSION_INIT); + DurableUtils.writeNullableString(out, uri.toString()); + DurableUtils.writeNullableString(out, mimeType); + DurableUtils.writeNullableString(out, displayName); + out.writeLong(lastModified); + out.writeInt(flags); + DurableUtils.writeNullableString(out, summary); + out.writeLong(size); + out.writeInt(icon); + } + + public static DocumentInfo fromDirectoryCursor(Cursor cursor) { + final DocumentInfo doc = new DocumentInfo(); + final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); + final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); + doc.uri = DocumentsContract.buildDocumentUri(authority, docId); + doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); + doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS); + doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY); + doc.size = getCursorLong(cursor, Document.COLUMN_SIZE); + doc.icon = getCursorInt(cursor, Document.COLUMN_ICON); + return doc; + } + + @Deprecated + public static DocumentInfo fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) + throws FileNotFoundException { + final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI)); + final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP); + + Cursor cursor = null; + try { + cursor = resolver.query(uri, null, null, null, null); + if (!cursor.moveToFirst()) { + throw new FileNotFoundException("Missing details for " + uri); + } + + final DocumentInfo doc = new DocumentInfo(); + doc.uri = uri; + doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + doc.lastModified = lastModified; + doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS) + & Document.FLAG_SUPPORTS_THUMBNAIL; + doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY); + doc.size = getCursorLong(cursor, Document.COLUMN_SIZE); + doc.icon = getCursorInt(cursor, Document.COLUMN_ICON); + return doc; + } catch (Throwable t) { + throw asFileNotFoundException(t); + } finally { + IoUtils.closeQuietly(cursor); + } + } + + public static DocumentInfo fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException { + Cursor cursor = null; + try { + cursor = resolver.query(uri, null, null, null, null); + if (!cursor.moveToFirst()) { + throw new FileNotFoundException("Missing details for " + uri); + } + final DocumentInfo doc = new DocumentInfo(); + doc.uri = uri; + doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); + doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS); + doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY); + doc.size = getCursorLong(cursor, Document.COLUMN_SIZE); + doc.icon = getCursorInt(cursor, Document.COLUMN_ICON); + return doc; + } catch (Throwable t) { + throw asFileNotFoundException(t); + } finally { + IoUtils.closeQuietly(cursor); + } + } + + @Override + public String toString() { + return "Document{name=" + displayName + ", uri=" + uri + "}"; + } + + public boolean isCreateSupported() { + return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0; + } + + public boolean isSearchSupported() { + return (flags & Document.FLAG_DIR_SUPPORTS_SEARCH) != 0; + } + + public boolean isThumbnailSupported() { + return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; + } + + public boolean isDirectory() { + return Document.MIME_TYPE_DIR.equals(mimeType); + } + + public boolean isGridPreferred() { + return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0; + } + + public boolean isDeleteSupported() { + return (flags & Document.FLAG_SUPPORTS_DELETE) != 0; + } + + public Drawable loadIcon(Context context) { + return loadIcon(context, uri.getAuthority(), icon); + } + + public static Drawable loadIcon(Context context, String authority, int icon) { + if (icon != 0) { + if (authority != null) { + final PackageManager pm = context.getPackageManager(); + final ProviderInfo info = pm.resolveContentProvider(authority, 0); + if (info != null) { + return pm.getDrawable(info.packageName, icon, info.applicationInfo); + } + } else { + return context.getResources().getDrawable(icon); + } + } + return null; + } + + public static String getCursorString(Cursor cursor, String columnName) { + final int index = cursor.getColumnIndex(columnName); + return (index != -1) ? cursor.getString(index) : null; + } + + /** + * Missing or null values are returned as -1. + */ + public static long getCursorLong(Cursor cursor, String columnName) { + final int index = cursor.getColumnIndex(columnName); + if (index == -1) return -1; + final String value = cursor.getString(index); + if (value == null) return -1; + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return -1; + } + } + + public static int getCursorInt(Cursor cursor, String columnName) { + final int index = cursor.getColumnIndex(columnName); + return (index != -1) ? cursor.getInt(index) : 0; + } + + @Deprecated + public static class DisplayNameComparator implements Comparator<DocumentInfo> { + @Override + public int compare(DocumentInfo lhs, DocumentInfo rhs) { + final boolean leftDir = lhs.isDirectory(); + final boolean rightDir = rhs.isDirectory(); + + if (leftDir != rightDir) { + return leftDir ? -1 : 1; + } else { + return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName); + } + } + } + + @Deprecated + public static class LastModifiedComparator implements Comparator<DocumentInfo> { + @Override + public int compare(DocumentInfo lhs, DocumentInfo rhs) { + return Long.compare(rhs.lastModified, lhs.lastModified); + } + } + + @Deprecated + public static class SizeComparator implements Comparator<DocumentInfo> { + @Override + public int compare(DocumentInfo lhs, DocumentInfo rhs) { + return Long.compare(rhs.size, lhs.size); + } + } + + public static FileNotFoundException asFileNotFoundException(Throwable t) + throws FileNotFoundException { + if (t instanceof FileNotFoundException) { + throw (FileNotFoundException) t; + } + final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage()); + fnfe.initCause(t); + throw fnfe; + } + + public static int compareToIgnoreCaseNullable(String lhs, String rhs) { + if (lhs == null) return -1; + if (rhs == null) return 1; + return lhs.compareToIgnoreCase(rhs); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index 81f75d2..64631ab 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -16,57 +16,22 @@ package com.android.documentsui.model; -import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.model.Document.asFileNotFoundException; - -import android.content.ContentResolver; -import android.net.Uri; -import android.provider.DocumentsContract.DocumentRoot; -import android.util.Log; - import com.android.documentsui.RootsCache; -import org.json.JSONArray; -import org.json.JSONException; - -import java.io.FileNotFoundException; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ProtocolException; import java.util.LinkedList; /** - * Representation of a stack of {@link Document}, usually the result of a + * Representation of a stack of {@link DocumentInfo}, usually the result of a * user-driven traversal. */ -public class DocumentStack extends LinkedList<Document> { - - public static String serialize(DocumentStack stack) { - final JSONArray json = new JSONArray(); - for (int i = 0; i < stack.size(); i++) { - json.put(stack.get(i).uri); - } - return json.toString(); - } - - public static DocumentStack deserialize(ContentResolver resolver, String raw) - throws FileNotFoundException { - Log.d(TAG, "deserialize: " + raw); +public class DocumentStack extends LinkedList<DocumentInfo> implements Durable { + private static final int VERSION_INIT = 1; - final DocumentStack stack = new DocumentStack(); - try { - final JSONArray json = new JSONArray(raw); - for (int i = 0; i < json.length(); i++) { - final Uri uri = Uri.parse(json.getString(i)); - final Document doc = Document.fromUri(resolver, uri); - stack.add(doc); - } - } catch (JSONException e) { - throw asFileNotFoundException(e); - } - - // TODO: handle roots that have gone missing - return stack; - } - - public DocumentRoot getRoot(RootsCache roots) { + public RootInfo getRoot(RootsCache roots) { return roots.findRoot(getLast().uri); } @@ -79,4 +44,37 @@ public class DocumentStack extends LinkedList<Document> { return null; } } + + @Override + public void reset() { + clear(); + } + + @Override + public void read(DataInputStream in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_INIT: + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final DocumentInfo doc = new DocumentInfo(); + doc.read(in); + add(doc); + } + break; + default: + throw new ProtocolException("Unknown version " + version); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(VERSION_INIT); + final int size = size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + final DocumentInfo doc = get(i); + doc.write(out); + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java b/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java new file mode 100644 index 0000000..01633ed --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui.model; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public interface Durable { + public void reset(); + public void read(DataInputStream in) throws IOException; + public void write(DataOutputStream out) throws IOException; +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java b/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java new file mode 100644 index 0000000..214fb14 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui.model; + +import static com.android.documentsui.DocumentsActivity.TAG; + +import android.os.BadParcelableException; +import android.os.Parcel; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class DurableUtils { + public static <D extends Durable> byte[] writeToArray(D d) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + d.write(new DataOutputStream(out)); + return out.toByteArray(); + } + + public static <D extends Durable> D readFromArray(byte[] data, D d) throws IOException { + final ByteArrayInputStream in = new ByteArrayInputStream(data); + d.reset(); + try { + d.read(new DataInputStream(in)); + } catch (IOException e) { + d.reset(); + throw e; + } + return d; + } + + public static <D extends Durable> byte[] writeToArrayOrNull(D d) { + try { + return writeToArray(d); + } catch (IOException e) { + Log.w(TAG, "Failed to write", e); + return null; + } + } + + public static <D extends Durable> D readFromArrayOrNull(byte[] data, D d) { + try { + return readFromArray(data, d); + } catch (IOException e) { + Log.w(TAG, "Failed to read", e); + return null; + } + } + + public static <D extends Durable> void writeToParcel(Parcel parcel, D d) { + try { + parcel.writeByteArray(writeToArray(d)); + } catch (IOException e) { + throw new BadParcelableException(e); + } + } + + public static <D extends Durable> D readFromParcel(Parcel parcel, D d) { + try { + return readFromArray(parcel.createByteArray(), d); + } catch (IOException e) { + throw new BadParcelableException(e); + } + } + + public static void writeNullableString(DataOutputStream out, String value) throws IOException { + if (value != null) { + out.write(1); + out.writeUTF(value); + } else { + out.write(0); + } + } + + public static String readNullableString(DataInputStream in) throws IOException { + if (in.read() != 0) { + return in.readUTF(); + } else { + return null; + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java new file mode 100644 index 0000000..189284b --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui.model; + +import static com.android.documentsui.model.DocumentInfo.getCursorInt; +import static com.android.documentsui.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.provider.DocumentsContract.Root; + +import java.util.Objects; + +/** + * Representation of a {@link Root}. + */ +public class RootInfo { + public String authority; + public String rootId; + public int rootType; + public int flags; + public int icon; + public String title; + public String summary; + public String documentId; + public long availableBytes; + + public static RootInfo fromRootsCursor(String authority, Cursor cursor) { + final RootInfo root = new RootInfo(); + root.authority = authority; + root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID); + root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE); + root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS); + root.icon = getCursorInt(cursor, Root.COLUMN_ICON); + root.title = getCursorString(cursor, Root.COLUMN_TITLE); + root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY); + root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID); + root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES); + return root; + } + + public Drawable loadIcon(Context context) { + return DocumentInfo.loadIcon(context, authority, icon); + } + + @Override + public boolean equals(Object o) { + if (o instanceof RootInfo) { + final RootInfo root = (RootInfo) o; + return Objects.equals(authority, root.authority) && Objects.equals(rootId, root.rootId); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(authority, rootId); + } + + public String getDirectoryString() { + return (summary != null) ? summary : title; + } +} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 583ecc9..bbe3b45 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -26,9 +26,8 @@ import android.media.ExifInterface; import android.os.CancellationSignal; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.DocumentRoot; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.webkit.MimeTypeMap; @@ -41,7 +40,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; -import java.util.List; import java.util.Map; public class ExternalStorageProvider extends DocumentsProvider { @@ -49,36 +47,52 @@ public class ExternalStorageProvider extends DocumentsProvider { // docId format: root:path/to/file - private static final String[] SUPPORTED_COLUMNS = new String[] { - DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, - DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS + private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, + Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, + Root.COLUMN_AVAILABLE_BYTES, }; - private ArrayList<DocumentRoot> mRoots; - private HashMap<String, DocumentRoot> mTagToRoot; - private HashMap<String, File> mTagToPath; + private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { + Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, + }; + + private static class RootInfo { + public String rootId; + public int rootType; + public int flags; + public int icon; + public String title; + public String docId; + } + + private ArrayList<RootInfo> mRoots; + private HashMap<String, RootInfo> mIdToRoot; + private HashMap<String, File> mIdToPath; @Override public boolean onCreate() { mRoots = Lists.newArrayList(); - mTagToRoot = Maps.newHashMap(); - mTagToPath = Maps.newHashMap(); + mIdToRoot = Maps.newHashMap(); + mIdToPath = Maps.newHashMap(); // TODO: support multiple storage devices try { - final String tag = "primary"; + final String rootId = "primary"; final File path = Environment.getExternalStorageDirectory(); - mTagToPath.put(tag, path); + mIdToPath.put(rootId, path); - final DocumentRoot root = new DocumentRoot(); - root.docId = getDocIdForFile(path); - root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED; - root.title = getContext().getString(R.string.root_internal_storage); + final RootInfo root = new RootInfo(); + root.rootId = "primary"; + root.rootType = Root.ROOT_TYPE_DEVICE; + root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED; root.icon = R.drawable.ic_pdf; - root.flags = DocumentRoot.FLAG_LOCAL_ONLY; + root.title = getContext().getString(R.string.root_internal_storage); + root.docId = getDocIdForFile(path); mRoots.add(root); - mTagToRoot.put(tag, root); + mIdToRoot.put(rootId, root); } catch (FileNotFoundException e) { throw new IllegalStateException(e); } @@ -86,12 +100,20 @@ public class ExternalStorageProvider extends DocumentsProvider { return true; } + private static String[] resolveRootProjection(String[] projection) { + return projection != null ? projection : DEFAULT_ROOT_PROJECTION; + } + + private static String[] resolveDocumentProjection(String[] projection) { + return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; + } + private String getDocIdForFile(File file) throws FileNotFoundException { String path = file.getAbsolutePath(); // Find the most-specific root path Map.Entry<String, File> mostSpecific = null; - for (Map.Entry<String, File> root : mTagToPath.entrySet()) { + for (Map.Entry<String, File> root : mIdToPath.entrySet()) { final String rootPath = root.getValue().getPath(); if (path.startsWith(rootPath) && (mostSpecific == null || rootPath.length() > mostSpecific.getValue().getPath().length())) { @@ -121,7 +143,7 @@ public class ExternalStorageProvider extends DocumentsProvider { final String tag = docId.substring(0, splitIndex); final String path = docId.substring(splitIndex + 1); - File target = mTagToPath.get(tag); + File target = mIdToPath.get(tag); if (target == null) { throw new FileNotFoundException("No root for " + tag); } @@ -143,41 +165,48 @@ public class ExternalStorageProvider extends DocumentsProvider { int flags = 0; if (file.isDirectory()) { - flags |= Documents.FLAG_SUPPORTS_SEARCH; + flags |= Document.FLAG_DIR_SUPPORTS_SEARCH; } if (file.isDirectory() && file.canWrite()) { - flags |= Documents.FLAG_SUPPORTS_CREATE; + flags |= Document.FLAG_DIR_SUPPORTS_CREATE; } if (file.canWrite()) { - flags |= Documents.FLAG_SUPPORTS_WRITE; - flags |= Documents.FLAG_SUPPORTS_RENAME; - flags |= Documents.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_WRITE; + flags |= Document.FLAG_SUPPORTS_DELETE; } final String displayName = file.getName(); final String mimeType = getTypeForFile(file); if (mimeType.startsWith("image/")) { - flags |= Documents.FLAG_SUPPORTS_THUMBNAIL; + flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } final RowBuilder row = result.newRow(); - row.offer(DocumentColumns.DOC_ID, docId); - row.offer(DocumentColumns.DISPLAY_NAME, displayName); - row.offer(DocumentColumns.SIZE, file.length()); - row.offer(DocumentColumns.MIME_TYPE, mimeType); - row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified()); - row.offer(DocumentColumns.FLAGS, flags); + row.offer(Document.COLUMN_DOCUMENT_ID, docId); + row.offer(Document.COLUMN_DISPLAY_NAME, displayName); + row.offer(Document.COLUMN_SIZE, file.length()); + row.offer(Document.COLUMN_MIME_TYPE, mimeType); + row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified()); + row.offer(Document.COLUMN_FLAGS, flags); } @Override - public List<DocumentRoot> getDocumentRoots() { - // Update free space - for (String tag : mTagToRoot.keySet()) { - final DocumentRoot root = mTagToRoot.get(tag); - final File path = mTagToPath.get(tag); - root.availableBytes = path.getFreeSpace(); + public Cursor queryRoots(String[] projection) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + for (String rootId : mIdToPath.keySet()) { + final RootInfo root = mIdToRoot.get(rootId); + final File path = mIdToPath.get(rootId); + + final RowBuilder row = result.newRow(); + row.offer(Root.COLUMN_ROOT_ID, root.rootId); + row.offer(Root.COLUMN_ROOT_TYPE, root.rootType); + row.offer(Root.COLUMN_FLAGS, root.flags); + row.offer(Root.COLUMN_ICON, root.icon); + row.offer(Root.COLUMN_TITLE, root.title); + row.offer(Root.COLUMN_DOCUMENT_ID, root.docId); + row.offer(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace()); } - return mRoots; + return result; } @Override @@ -187,7 +216,7 @@ public class ExternalStorageProvider extends DocumentsProvider { displayName = validateDisplayName(mimeType, displayName); final File file = new File(parent, displayName); - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (Document.MIME_TYPE_DIR.equals(mimeType)) { if (!file.mkdir()) { throw new IllegalStateException("Failed to mkdir " + file); } @@ -204,16 +233,6 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public void renameDocument(String docId, String displayName) throws FileNotFoundException { - final File file = getFileForDocId(docId); - final File newFile = new File(file.getParentFile(), displayName); - if (!file.renameTo(newFile)) { - throw new IllegalStateException("Failed to rename " + docId); - } - // TODO: update any outstanding grants - } - - @Override public void deleteDocument(String docId) throws FileNotFoundException { final File file = getFileForDocId(docId); if (!file.delete()) { @@ -222,16 +241,19 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public Cursor queryDocument(String docId) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); - includeFile(result, docId, null); + public Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + includeFile(result, documentId, null); return result; } @Override - public Cursor queryDocumentChildren(String docId) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); - final File parent = getFileForDocId(docId); + public Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(parentDocumentId); for (File file : parent.listFiles()) { includeFile(result, null, file); } @@ -239,9 +261,10 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public Cursor querySearch(String docId, String query) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); - final File parent = getFileForDocId(docId); + public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(parentDocumentId); final LinkedList<File> pending = new LinkedList<File>(); pending.add(parent); @@ -261,22 +284,24 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public String getType(String docId) throws FileNotFoundException { - final File file = getFileForDocId(docId); + public String getDocumentType(String documentId) throws FileNotFoundException { + final File file = getFileForDocId(documentId); return getTypeForFile(file); } @Override - public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) + public ParcelFileDescriptor openDocument( + String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { - final File file = getFileForDocId(docId); + final File file = getFileForDocId(documentId); return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode)); } @Override public AssetFileDescriptor openDocumentThumbnail( - String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { - final File file = getFileForDocId(docId); + String documentId, Point sizeHint, CancellationSignal signal) + throws FileNotFoundException { + final File file = getFileForDocId(documentId); final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( file, ParcelFileDescriptor.MODE_READ_ONLY); @@ -294,7 +319,7 @@ public class ExternalStorageProvider extends DocumentsProvider { private static String getTypeForFile(File file) { if (file.isDirectory()) { - return Documents.MIME_TYPE_DIR; + return Document.MIME_TYPE_DIR; } else { return getTypeForName(file.getName()); } @@ -314,7 +339,7 @@ public class ExternalStorageProvider extends DocumentsProvider { } private static String validateDisplayName(String mimeType, String displayName) { - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (Document.MIME_TYPE_DIR.equals(mimeType)) { return displayName; } else { // Try appending meaningful extension if needed diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index 8fb3998..1b8876d 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -417,9 +417,6 @@ class TouchExplorer implements EventStreamTransformation { mSendTouchInteractionEndDelayed.forceSendAndRemove(); } - // Cache the event until we discern exploration from gesturing. - mSendHoverEnterAndMoveDelayed.addEvent(event); - // If we have the first tap, schedule a long press and break // since we do not want to schedule hover enter because // the delayed callback will kick in before the long click. @@ -432,11 +429,16 @@ class TouchExplorer implements EventStreamTransformation { break; } if (!mTouchExplorationInProgress) { - // Deliver hover enter with a delay to have a chance - // to detect what the user is trying to do. - final int pointerId = receivedTracker.getPrimaryPointerId(); - final int pointerIdBits = (1 << pointerId); - mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags); + if (!mSendHoverEnterAndMoveDelayed.isPending()) { + // Deliver hover enter with a delay to have a chance + // to detect what the user is trying to do. + final int pointerId = receivedTracker.getPrimaryPointerId(); + final int pointerIdBits = (1 << pointerId); + mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, + policyFlags); + } + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); } } break; case MotionEvent.ACTION_POINTER_DOWN: { @@ -1719,7 +1721,7 @@ class TouchExplorer implements EventStreamTransformation { } break; } if (DEBUG) { - Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString()); + Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString()); } } @@ -1777,7 +1779,7 @@ class TouchExplorer implements EventStreamTransformation { */ public int getPrimaryPointerId() { if (mPrimaryPointerId == INVALID_POINTER_ID) { - mPrimaryPointerId = findPrimaryPointer(); + mPrimaryPointerId = findPrimaryPointerId(); } return mPrimaryPointerId; } @@ -1861,17 +1863,21 @@ class TouchExplorer implements EventStreamTransformation { } /** - * @return The primary pointer. + * @return The primary pointer id. */ - private int findPrimaryPointer() { + private int findPrimaryPointerId() { int primaryPointerId = INVALID_POINTER_ID; long minDownTime = Long.MAX_VALUE; + // Find the pointer that went down first. - for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) { - final long downPointerTime = mReceivedPointerDownTime[i]; + int pointerIdBits = mReceivedPointersDown; + while (pointerIdBits > 0) { + final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits); + pointerIdBits &= ~(1 << pointerId); + final long downPointerTime = mReceivedPointerDownTime[pointerId]; if (downPointerTime < minDownTime) { minDownTime = downPointerTime; - primaryPointerId = i; + primaryPointerId = pointerId; } } return primaryPointerId; |
