Merge "Re-enable -Wall and -Werror that were lost in a merge"
diff --git a/Android.mk b/Android.mk
index 2a94f3a..d9e4455 100644
--- a/Android.mk
+++ b/Android.mk
@@ -199,6 +199,7 @@
core/java/android/os/INetworkActivityListener.aidl \
core/java/android/os/INetworkManagementService.aidl \
core/java/android/os/IPermissionController.aidl \
+ core/java/android/os/IProcessInfoService.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/os/IRemoteCallback.aidl \
core/java/android/os/ISchedulingPolicyService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 144f292..ed80e38 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -547,6 +547,7 @@
field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6
field public static final int exported = 16842768; // 0x1010010
field public static final int extraTension = 16843371; // 0x101026b
+ field public static final int extractNativeLibs = 16843990; // 0x10104d6
field public static final int factor = 16843219; // 0x10101d3
field public static final int fadeDuration = 16843384; // 0x1010278
field public static final int fadeEnabled = 16843390; // 0x101027e
@@ -8368,6 +8369,7 @@
field public static final int FLAG_ALLOW_TASK_REPARENTING = 32; // 0x20
field public static final int FLAG_DEBUGGABLE = 2; // 0x2
field public static final int FLAG_EXTERNAL_STORAGE = 262144; // 0x40000
+ field public static final int FLAG_EXTRACT_NATIVE_LIBS = 268435456; // 0x10000000
field public static final int FLAG_FACTORY_TEST = 16; // 0x10
field public static final int FLAG_FULL_BACKUP_ONLY = 67108864; // 0x4000000
field public static final int FLAG_HAS_CODE = 4; // 0x4
@@ -17515,7 +17517,7 @@
method public static android.net.http.HttpResponseCache getInstalled();
method public int getNetworkCount();
method public int getRequestCount();
- method public static android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException;
+ method public static synchronized android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException;
method public long maxSize();
method public java.net.CacheRequest put(java.net.URI, java.net.URLConnection) throws java.io.IOException;
method public long size();
@@ -41519,7 +41521,7 @@
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
- method public static synchronized double random();
+ method public static double random();
method public static double rint(double);
method public static long round(double);
method public static int round(float);
diff --git a/api/system-current.txt b/api/system-current.txt
index 7ab0e48..8f1b8b2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -617,6 +617,7 @@
field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6
field public static final int exported = 16842768; // 0x1010010
field public static final int extraTension = 16843371; // 0x101026b
+ field public static final int extractNativeLibs = 16843990; // 0x10104d6
field public static final int factor = 16843219; // 0x10101d3
field public static final int fadeDuration = 16843384; // 0x1010278
field public static final int fadeEnabled = 16843390; // 0x101027e
@@ -8616,6 +8617,7 @@
field public static final int FLAG_ALLOW_TASK_REPARENTING = 32; // 0x20
field public static final int FLAG_DEBUGGABLE = 2; // 0x2
field public static final int FLAG_EXTERNAL_STORAGE = 262144; // 0x40000
+ field public static final int FLAG_EXTRACT_NATIVE_LIBS = 268435456; // 0x10000000
field public static final int FLAG_FACTORY_TEST = 16; // 0x10
field public static final int FLAG_FULL_BACKUP_ONLY = 67108864; // 0x4000000
field public static final int FLAG_HAS_CODE = 4; // 0x4
@@ -18843,7 +18845,7 @@
method public static android.net.http.HttpResponseCache getInstalled();
method public int getNetworkCount();
method public int getRequestCount();
- method public static android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException;
+ method public static synchronized android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException;
method public long maxSize();
method public java.net.CacheRequest put(java.net.URI, java.net.URLConnection) throws java.io.IOException;
method public long size();
@@ -44055,7 +44057,7 @@
method public static double nextUp(double);
method public static float nextUp(float);
method public static double pow(double, double);
- method public static synchronized double random();
+ method public static double random();
method public static double rint(double);
method public static long round(double);
method public static int round(float);
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index c86fd53..c5af992 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -7,6 +7,12 @@
#define LOG_TAG "appproc"
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
@@ -17,11 +23,6 @@
#include <android_runtime/AndroidRuntime.h>
#include <private/android_filesystem_config.h> // for AID_SYSTEM
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/prctl.h>
-
namespace android {
static void app_usage()
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index 197e36b..84158d3 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -1,3 +1,6 @@
+#include <dirent.h>
+#include <sys/stat.h>
+
#include "idmap.h"
#include <UniquePtr.h>
@@ -9,8 +12,6 @@
#include <utils/String16.h>
#include <utils/String8.h>
-#include <dirent.h>
-
#define NO_OVERLAY_TAG (-1000)
using namespace android;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7a636db..c6ffef6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -256,6 +256,9 @@
/** @hide User operation call: given user id is the current user, can't be stopped. */
public static final int USER_OP_IS_CURRENT = -2;
+ /** @hide Process does not exist. */
+ public static final int PROCESS_STATE_NONEXISTENT = -1;
+
/** @hide Process is a persistent system process. */
public static final int PROCESS_STATE_PERSISTENT = 0;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bdd9e41..9269f60 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -97,7 +97,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptCacheDir;
import android.security.AndroidKeyStoreProvider;
import com.android.internal.app.IVoiceInteractor;
@@ -257,18 +257,21 @@
}
}
+ static final class AcquiringProviderRecord {
+ IActivityManager.ContentProviderHolder holder;
+ boolean acquiring = true;
+ int requests = 1;
+ }
+
// The lock of mProviderMap protects the following variables.
- final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
- = new ArrayMap<ProviderKey, ProviderClientRecord>();
- final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap
- = new ArrayMap<IBinder, ProviderRefCount>();
- final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders
- = new ArrayMap<IBinder, ProviderClientRecord>();
- final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName
- = new ArrayMap<ComponentName, ProviderClientRecord>();
+ final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<>();
+ final ArrayMap<ProviderKey, AcquiringProviderRecord> mAcquiringProviderMap = new ArrayMap<>();
+ final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap = new ArrayMap<>();
+ final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders = new ArrayMap<>();
+ final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName = new ArrayMap<>();
final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
- = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
+ = new ArrayMap<>();
final GcIdler mGcIdler = new GcIdler();
boolean mGcIdlerScheduled = false;
@@ -345,7 +348,7 @@
}
}
- final class ProviderClientRecord {
+ static final class ProviderClientRecord {
final String[] mNames;
final IContentProvider mProvider;
final ContentProvider mLocalProvider;
@@ -3184,7 +3187,7 @@
if (cv == null) {
mThumbnailCanvas = cv = new Canvas();
}
-
+
cv.setBitmap(thumbnail);
if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
mAvailThumbnailBitmap = thumbnail;
@@ -3482,12 +3485,12 @@
private void handleWindowVisibility(IBinder token, boolean show) {
ActivityClientRecord r = mActivities.get(token);
-
+
if (r == null) {
Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
return;
}
-
+
if (!show && !r.stopped) {
performStopActivityInner(r, null, show, false);
} else if (show && r.stopped) {
@@ -3915,10 +3918,10 @@
}
}
}
-
+
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+ tmp.token + ": changedConfig=" + changedConfig);
-
+
// If there was a pending configuration change, execute it first.
if (changedConfig != null) {
mCurDefaultDisplayDpi = changedConfig.densityDpi;
@@ -4115,7 +4118,7 @@
if (config == null) {
return;
}
-
+
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
@@ -4163,7 +4166,7 @@
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name);
-
+
performConfigurationChanged(r.activity, mCompatConfiguration);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
@@ -4244,7 +4247,7 @@
ApplicationPackageManager.handlePackageBroadcast(cmd, packages,
hasPkgInfo);
}
-
+
final void handleLowMemory() {
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
@@ -4291,10 +4294,10 @@
String[] packages = getPackageManager().getPackagesForUid(uid);
// If there are several packages in this application we won't
- // initialize the graphics disk caches
+ // initialize the graphics disk caches
if (packages != null && packages.length == 1) {
HardwareRenderer.setupDiskCache(cacheDir);
- RenderScript.setupDiskCache(cacheDir);
+ RenderScriptCacheDir.setupDiskCache(cacheDir);
}
} catch (RemoteException e) {
// Ignore
@@ -4648,22 +4651,57 @@
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
- final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
+ final ProviderKey key = new ProviderKey(auth, userId);
+ final IContentProvider provider = acquireExistingProvider(c, key, stable);
if (provider != null) {
return provider;
}
+ AcquiringProviderRecord r;
+ boolean first = false;
+ synchronized (mAcquiringProviderMap) {
+ r = mAcquiringProviderMap.get(key);
+ if (r == null) {
+ r = new AcquiringProviderRecord();
+ mAcquiringProviderMap.put(key, r);
+ first = true;
+ } else {
+ r.requests++;
+ }
+ }
- // There is a possible race here. Another thread may try to acquire
- // the same provider at the same time. When this happens, we want to ensure
- // that the first one wins.
- // Note that we cannot hold the lock while acquiring and installing the
- // provider since it might take a long time to run and it could also potentially
- // be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
- try {
- holder = ActivityManagerNative.getDefault().getContentProvider(
- getApplicationThread(), auth, userId, stable);
- } catch (RemoteException ex) {
+ if (first) {
+ // Multiple threads may try to acquire the same provider at the same time.
+ // When this happens, we only let the first one really gets provider.
+ // Other threads just wait for its result.
+ // Note that we cannot hold the lock while acquiring and installing the
+ // provider since it might take a long time to run and it could also potentially
+ // be re-entrant in the case where the provider is in the same process.
+ try {
+ holder = ActivityManagerNative.getDefault().getContentProvider(
+ getApplicationThread(), auth, userId, stable);
+ } catch (RemoteException ex) {
+ }
+ synchronized (r) {
+ r.holder = holder;
+ r.acquiring = false;
+ r.notifyAll();
+ }
+ } else {
+ synchronized (r) {
+ while (r.acquiring) {
+ try {
+ r.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ holder = r.holder;
+ }
+ }
+ synchronized (mAcquiringProviderMap) {
+ if (--r.requests == 0) {
+ mAcquiringProviderMap.remove(key);
+ }
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
@@ -4747,8 +4785,12 @@
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
+ return acquireExistingProvider(c, new ProviderKey(auth, userId), stable);
+ }
+
+ final IContentProvider acquireExistingProvider(
+ Context c, ProviderKey key, boolean stable) {
synchronized (mProviderMap) {
- final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
@@ -4759,7 +4801,7 @@
if (!jBinder.isBinderAlive()) {
// The hosting process of the provider has died; we can't
// use this one.
- Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId
+ ": existing object's process dead");
handleUnstableProviderDiedLocked(jBinder, true);
return null;
@@ -5081,18 +5123,12 @@
if (DEBUG_PROVIDER) {
Slog.v(TAG, "installProvider: lost the race, updating ref count");
}
- // We need to transfer our new reference to the existing
- // ref count, releasing the old one... but only if
- // release is needed (that is, it is not running in the
- // system process).
+ // The provider has already been installed, so we need
+ // to increase reference count to the existing one, but
+ // only if release is needed (that is, it is not running
+ // in the system process or local to the process).
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
- try {
- ActivityManagerNative.getDefault().removeContentProvider(
- holder.connection, stable);
- } catch (RemoteException e) {
- //do nothing content provider object is dead any way
- }
}
} else {
ProviderClientRecord client = installProviderAuthoritiesLocked(
@@ -5186,7 +5222,7 @@
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
-
+
sendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 6c2511e..8692336 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -235,10 +235,13 @@
dest.writeString(processName);
dest.writeLong(time);
dest.writeInt(systemApp ? 1 : 0);
+ dest.writeInt(crashInfo != null ? 1 : 0);
switch (type) {
case TYPE_CRASH:
- crashInfo.writeToParcel(dest, flags);
+ if (crashInfo != null) {
+ crashInfo.writeToParcel(dest, flags);
+ }
break;
case TYPE_ANR:
anrInfo.writeToParcel(dest, flags);
@@ -259,10 +262,11 @@
processName = in.readString();
time = in.readLong();
systemApp = in.readInt() == 1;
+ boolean hasCrashInfo = in.readInt() == 1;
switch (type) {
case TYPE_CRASH:
- crashInfo = new CrashInfo(in);
+ crashInfo = hasCrashInfo ? new CrashInfo(in) : null;
anrInfo = null;
batteryInfo = null;
runningServiceInfo = null;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 05c19db..4a087da 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -346,6 +346,11 @@
public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27;
/**
+ * When set installer extracts native libs from .apk files.
+ */
+ public static final int FLAG_EXTRACT_NATIVE_LIBS = 1<<28;
+
+ /**
* Value for {@link #flags}: true if code from this application will need to be
* loaded into other applications' processes. On devices that support multiple
* instruction sets, this implies the code might be loaded into a process that's
@@ -900,6 +905,20 @@
/**
* @hide
*/
+ public boolean isSystemApp() {
+ return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isUpdatedSystemApp() {
+ return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return this;
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4952ba1..53aa6ff 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -268,6 +268,7 @@
public final boolean coreApp;
public final boolean multiArch;
+ public final boolean extractNativeLibs;
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
String[] splitCodePaths, int[] splitRevisionCodes) {
@@ -283,6 +284,7 @@
this.splitRevisionCodes = splitRevisionCodes;
this.coreApp = baseApk.coreApp;
this.multiArch = baseApk.multiArch;
+ this.extractNativeLibs = baseApk.extractNativeLibs;
}
public List<String> getAllCodePaths() {
@@ -309,10 +311,12 @@
public final Signature[] signatures;
public final boolean coreApp;
public final boolean multiArch;
+ public final boolean extractNativeLibs;
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
int revisionCode, int installLocation, List<VerifierInfo> verifiers,
- Signature[] signatures, boolean coreApp, boolean multiArch) {
+ Signature[] signatures, boolean coreApp, boolean multiArch,
+ boolean extractNativeLibs) {
this.codePath = codePath;
this.packageName = packageName;
this.splitName = splitName;
@@ -323,6 +327,7 @@
this.signatures = signatures;
this.coreApp = coreApp;
this.multiArch = multiArch;
+ this.extractNativeLibs = extractNativeLibs;
}
}
@@ -1269,6 +1274,7 @@
int revisionCode = 0;
boolean coreApp = false;
boolean multiArch = false;
+ boolean extractNativeLibs = true;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
final String attr = attrs.getAttributeName(i);
@@ -1307,14 +1313,17 @@
final String attr = attrs.getAttributeName(i);
if ("multiArch".equals(attr)) {
multiArch = attrs.getAttributeBooleanValue(i, false);
- break;
+ }
+ if ("extractNativeLibs".equals(attr)) {
+ extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
}
}
}
}
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
- revisionCode, installLocation, verifiers, signatures, coreApp, multiArch);
+ revisionCode, installLocation, verifiers, signatures, coreApp, multiArch,
+ extractNativeLibs);
}
/**
@@ -2567,6 +2576,12 @@
ai.flags |= ApplicationInfo.FLAG_MULTIARCH;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_extractNativeLibs,
+ true)) {
+ ai.flags |= ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS;
+ }
+
String str;
str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_permission, 0);
@@ -4444,6 +4459,20 @@
return applicationInfo.isForwardLocked();
}
+ /**
+ * @hide
+ */
+ public boolean isSystemApp() {
+ return applicationInfo.isSystemApp();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isUpdatedSystemApp() {
+ return applicationInfo.isUpdatedSystemApp();
+ }
+
public String toString() {
return "Package{"
+ Integer.toHexString(System.identityHashCode(this))
diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java
index 5696839..e902443 100644
--- a/core/java/android/inputmethodservice/ExtractEditLayout.java
+++ b/core/java/android/inputmethodservice/ExtractEditLayout.java
@@ -163,6 +163,8 @@
mCallback.onDestroyActionMode(this);
mCallback = null;
+ mMenu.close();
+
mExtractActionButton.setVisibility(VISIBLE);
mEditButton.setVisibility(INVISIBLE);
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 2099c3f..fb2f445 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -384,6 +384,11 @@
}
}
return builder.toString();
+ } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
+ || scheme.equalsIgnoreCase("ftp")) {
+ ssp = "//" + ((getHost() != null) ? getHost() : "")
+ + ((getPort() != -1) ? (":" + getPort()) : "")
+ + "/...";
}
}
// Not a sensitive scheme, but let's still be conservative about
diff --git a/core/java/android/os/IProcessInfoService.aidl b/core/java/android/os/IProcessInfoService.aidl
new file mode 100644
index 0000000..c98daa2
--- /dev/null
+++ b/core/java/android/os/IProcessInfoService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** {@hide} */
+interface IProcessInfoService
+{
+ /**
+ * For each PID in the given input array, write the current process state
+ * for that process into the output array, or ActivityManager.PROCESS_STATE_NONEXISTENT
+ * to indicate that no process with the given PID exists.
+ */
+ void getProcessStatesFromPids(in int[] pids, out int[] states);
+}
+
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 0224c73..d989cd1 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -1426,7 +1426,7 @@
protected boolean persistString(String value) {
if (shouldPersist()) {
// Shouldn't store null
- if (value == getPersistedString(null)) {
+ if (TextUtils.equals(value, getPersistedString(null))) {
// It's already there, so the same as persisting
return true;
}
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 14b5748..579cdbe 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -73,4 +73,6 @@
OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input);
OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature);
int abort(IBinder handle);
+ boolean isOperationAuthorized(IBinder token);
+ int addAuthToken(in byte[] authToken);
}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e653b74..6176399 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -16,6 +16,9 @@
package android.security.keymaster;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Class tracking all the keymaster enum values needed for the binder API to keystore.
* This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h
@@ -168,6 +171,9 @@
public static final int KM_KEY_FORMAT_PKCS12 = 2;
public static final int KM_KEY_FORMAT_RAW = 3;
+ // User authenticators.
+ public static final int HW_AUTH_PASSWORD = 1 << 0;
+
// Error codes.
public static final int KM_ERROR_OK = 0;
public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
@@ -178,7 +184,7 @@
public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
- public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9;
+ public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9;
public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
@@ -224,7 +230,54 @@
public static final int KM_ERROR_VERSION_MISMATCH = -101;
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
+ public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>();
+ static {
+ sErrorCodeToString.put(KM_ERROR_OK, "OK");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH,
+ "Unsupported MAC or authentication tag length");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest");
+ sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time");
+ sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID");
+ sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
+ "Invalid user authorization timeout");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format");
+ sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length");
+ sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid");
+ sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired");
+ sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated");
+ sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle");
+ sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed");
+ sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations");
+ sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob");
+ sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag");
+ sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag");
+ sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field");
+ sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
+ sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
+ }
+
public static int getTagType(int tag) {
return tag & (0xF << 28);
}
+
+ public static String getErrorMessage(int errorCode) {
+ String result = sErrorCodeToString.get(errorCode);
+ if (result != null) {
+ return result;
+ }
+ return String.valueOf(errorCode);
+ }
}
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index ad54c96..7cc43d3 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -30,6 +30,7 @@
public class OperationResult implements Parcelable {
public final int resultCode;
public final IBinder token;
+ public final long operationHandle;
public final int inputConsumed;
public final byte[] output;
@@ -47,6 +48,7 @@
protected OperationResult(Parcel in) {
resultCode = in.readInt();
token = in.readStrongBinder();
+ operationHandle = in.readLong();
inputConsumed = in.readInt();
output = in.createByteArray();
}
@@ -60,6 +62,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeInt(resultCode);
out.writeStrongBinder(token);
+ out.writeLong(operationHandle);
out.writeInt(inputConsumed);
out.writeByteArray(output);
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 749f813..d751266 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -436,11 +436,7 @@
Request removeRequest(IBinder reqInterface) {
synchronized (this) {
- Request req = mActiveRequests.get(reqInterface);
- if (req != null) {
- mActiveRequests.remove(req);
- }
- return req;
+ return mActiveRequests.remove(reqInterface);
}
}
diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java
index 3ca6033..00f1493 100644
--- a/core/java/android/text/SpanSet.java
+++ b/core/java/android/text/SpanSet.java
@@ -17,6 +17,7 @@
package android.text;
import java.lang.reflect.Array;
+import java.util.Arrays;
/**
* A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then
@@ -54,6 +55,7 @@
spanFlags = new int[length];
}
+ int prevNumberOfSpans = numberOfSpans;
numberOfSpans = 0;
for (int i = 0; i < length; i++) {
final E span = allSpans[i];
@@ -71,6 +73,12 @@
numberOfSpans++;
}
+
+ // cleanup extra spans left over from previous init() call
+ if (numberOfSpans < prevNumberOfSpans) {
+ // prevNumberofSpans was > 0, therefore spans != null
+ Arrays.fill(spans, numberOfSpans, prevNumberOfSpans, null);
+ }
}
/**
@@ -103,9 +111,8 @@
* Removes all internal references to the spans to avoid memory leaks.
*/
public void recycle() {
- // The spans array is guaranteed to be not null when numberOfSpans is > 0
- for (int i = 0; i < numberOfSpans; i++) {
- spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled
+ if (spans != null) {
+ Arrays.fill(spans, 0, numberOfSpans, null);
}
}
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 50e64c6..a237afd 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1005,31 +1005,23 @@
return fields;
}
- final ArrayList<Field> declaredFields = new ArrayList();
- klass.getDeclaredFieldsUnchecked(false, declaredFields);
-
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- final int count = declaredFields.size();
- for (int i = 0; i < count; i++) {
- final Field field = declaredFields.get(i);
-
- // Ensure the field type can be resolved.
- try {
- field.getType();
- } catch (NoClassDefFoundError e) {
- continue;
+ try {
+ final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
+ final ArrayList<Field> foundFields = new ArrayList<Field>();
+ for (final Field field : declaredFields) {
+ // Fields which can't be resolved have a null type.
+ if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
+ field.setAccessible(true);
+ foundFields.add(field);
+ sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
+ }
}
-
- if (field.isAnnotationPresent(ExportedProperty.class)) {
- field.setAccessible(true);
- foundFields.add(field);
- sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
- }
+ fields = foundFields.toArray(new Field[foundFields.size()]);
+ map.put(klass, fields);
+ } catch (NoClassDefFoundError e) {
+ throw new AssertionError(e);
}
- fields = foundFields.toArray(new Field[foundFields.size()]);
- map.put(klass, fields);
-
return fields;
}
@@ -1651,4 +1643,4 @@
}
});
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index de1bbc7..358dbde 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -1812,9 +1812,7 @@
}
if (mRefreshProgressRunnable != null) {
removeCallbacks(mRefreshProgressRunnable);
- }
- if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
- removeCallbacks(mRefreshProgressRunnable);
+ mRefreshIsPosted = false;
}
if (mAccessibilityEventSender != null) {
removeCallbacks(mAccessibilityEventSender);
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 56bdb9b..5eaf20c 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -817,12 +817,12 @@
mContext = context;
mIntent = intent;
- mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
-
- mLayoutInflater = LayoutInflater.from(context);
if (mIntent == null) {
throw new IllegalArgumentException("Non-null Intent must be specified.");
}
+
+ mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
+ mLayoutInflater = LayoutInflater.from(context);
mRequestedViews = new RemoteViewsFrameLayoutRefSet();
// Strip the previously injected app widget id from service intent
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 02f675c..f479f4f 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -33,6 +33,7 @@
import android.content.pm.PackageParser.PackageParserException;
import android.os.Build;
import android.os.SELinux;
+import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Slog;
@@ -74,6 +75,7 @@
final long[] apkHandles;
final boolean multiArch;
+ final boolean extractNativeLibs;
public static Handle create(File packageFile) throws IOException {
try {
@@ -86,14 +88,16 @@
public static Handle create(Package pkg) throws IOException {
return create(pkg.getAllCodePaths(),
- (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0);
+ (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0,
+ (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0);
}
public static Handle create(PackageLite lite) throws IOException {
- return create(lite.getAllCodePaths(), lite.multiArch);
+ return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs);
}
- private static Handle create(List<String> codePaths, boolean multiArch) throws IOException {
+ private static Handle create(List<String> codePaths, boolean multiArch,
+ boolean extractNativeLibs) throws IOException {
final int size = codePaths.size();
final long[] apkHandles = new long[size];
for (int i = 0; i < size; i++) {
@@ -108,12 +112,13 @@
}
}
- return new Handle(apkHandles, multiArch);
+ return new Handle(apkHandles, multiArch, extractNativeLibs);
}
- Handle(long[] apkHandles, boolean multiArch) {
+ Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs) {
this.apkHandles = apkHandles;
this.multiArch = multiArch;
+ this.extractNativeLibs = extractNativeLibs;
mGuard.open("close");
}
@@ -146,8 +151,8 @@
private static native long nativeSumNativeBinaries(long handle, String cpuAbi);
- private native static int nativeCopyNativeBinaries(long handle,
- String sharedLibraryPath, String abiToCopy);
+ private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
+ String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge);
private static long sumNativeBinaries(Handle handle, String abi) {
long sum = 0;
@@ -167,7 +172,8 @@
*/
public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
for (long apkHandle : handle.apkHandles) {
- int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi);
+ int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
+ handle.extractNativeLibs, HAS_NATIVE_BRIDGE);
if (res != INSTALL_SUCCEEDED) {
return res;
}
@@ -218,7 +224,8 @@
/**
* Remove the native binaries of a given package. This deletes the files
*/
- public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir) {
+ public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot,
+ boolean deleteRootDir) {
if (DEBUG_NATIVE) {
Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath());
}
@@ -247,7 +254,8 @@
// asked to or this will prevent installation of future updates.
if (deleteRootDir) {
if (!nativeLibraryRoot.delete()) {
- Slog.w(TAG, "Could not delete native binary directory: " + nativeLibraryRoot.getPath());
+ Slog.w(TAG, "Could not delete native binary directory: " +
+ nativeLibraryRoot.getPath());
}
}
}
@@ -416,6 +424,9 @@
// We don't care about the other return values for now.
private static final int BITCODE_PRESENT = 1;
+ private static final boolean HAS_NATIVE_BRIDGE =
+ !"0".equals(SystemProperties.get("ro.dalvik.vm.native.bridge", "0"));
+
private static native int hasRenderscriptBitcode(long apkHandle);
public static boolean hasRenderscriptBitcode(Handle handle) throws IOException {
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 433a54b..a4cdf19 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -91,11 +91,11 @@
}
public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false);
+ return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false, null);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable) {
+ String instructionSet, boolean vmSafeMode, boolean debuggable, String outputPath) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
@@ -108,6 +108,8 @@
builder.append(instructionSet);
builder.append(vmSafeMode ? " 1" : " 0");
builder.append(debuggable ? " 1" : " 0");
+ builder.append(' ');
+ builder.append(outputPath != null ? outputPath : "!");
return execute(builder.toString());
}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ce50d96..0bfc0c9 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -536,7 +536,6 @@
*/
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
- int result = -1;
JavaVMInitArgs initArgs;
char propBuf[PROPERTY_VALUE_MAX];
char stackTraceFileBuf[sizeof("-Xstacktracefile:")-1 + PROPERTY_VALUE_MAX];
@@ -552,12 +551,19 @@
char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX];
+ char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX];
char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
+ char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
+ char dex2oatThreadsImageBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
+ char dex2oat_isa_variant_key[PROPERTY_KEY_MAX];
+ char dex2oat_isa_variant[sizeof("--instruction-set-variant=") -1 + PROPERTY_VALUE_MAX];
+ char dex2oat_isa_features_key[PROPERTY_KEY_MAX];
+ char dex2oat_isa_features[sizeof("--instruction-set-features=") -1 + PROPERTY_VALUE_MAX];
char dex2oatFlagsBuf[PROPERTY_VALUE_MAX];
char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX];
char extraOptsBuf[PROPERTY_VALUE_MAX];
@@ -580,6 +586,7 @@
char localeOption[sizeof("-Duser.locale=") + PROPERTY_VALUE_MAX];
char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:")-1 + PROPERTY_VALUE_MAX];
char nativeBridgeLibrary[sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX];
+ char cpuAbiListBuf[sizeof("--cpu-abilist=") + PROPERTY_VALUE_MAX];
bool checkJni = false;
property_get("dalvik.vm.checkjni", propBuf, "");
@@ -698,7 +705,7 @@
if (!hasFile("/system/etc/preloaded-classes")) {
ALOGE("Missing preloaded-classes file, /system/etc/preloaded-classes not found: %s\n",
strerror(errno));
- goto bail;
+ return -1;
}
addOption("-Ximage-compiler-option");
addOption("--image-classes=/system/etc/preloaded-classes");
@@ -732,6 +739,46 @@
parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf,
"--compiler-filter=", "-Xcompiler-option");
}
+ parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option");
+ parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
+ "-Ximage-compiler-option");
+
+ // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to
+ // pass the instruction-set-features/variant as an image-compiler-option.
+ // TODO: Find a better way for the instruction-set.
+#if defined(__arm__)
+ constexpr const char* instruction_set = "arm";
+#elif defined(__aarch64__)
+ constexpr const char* instruction_set = "arm64";
+#elif defined(__mips__) && !defined(__LP64__)
+ constexpr const char* instruction_set = "mips";
+#elif defined(__mips__) && defined(__LP64__)
+ constexpr const char* instruction_set = "mips64";
+#elif defined(__i386__)
+ constexpr const char* instruction_set = "x86";
+#elif defined(__x86_64__)
+ constexpr const char* instruction_set = "x86_64";
+#else
+ constexpr const char* instruction_set = "unknown";
+#endif
+ // Note: it is OK to reuse the buffer, as the values are exactly the same between
+ // * compiler-option, used for runtime compilation (DexClassLoader)
+ // * image-compiler-option, used for boot-image compilation on device
+
+ // Copy the variant.
+ sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", instruction_set);
+ parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
+ "--instruction-set-variant=", "-Ximage-compiler-option");
+ parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
+ "--instruction-set-variant=", "-Xcompiler-option");
+ // Copy the features.
+ sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", instruction_set);
+ parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
+ "--instruction-set-features=", "-Ximage-compiler-option");
+ parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
+ "--instruction-set-features=", "-Xcompiler-option");
+
+
property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, "");
parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option");
@@ -810,6 +857,22 @@
addOption(nativeBridgeLibrary);
}
+ // Dalvik-cache pruning counter.
+ parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf,
+ "-Xzygote-max-boot-retry=");
+#if defined(__LP64__)
+ const char* cpu_abilist_property_name = "ro.product.cpu.abilist64";
+#else
+ const char* cpu_abilist_property_name = "ro.product.cpu.abilist32";
+#endif // defined(__LP64__)
+ property_get(cpu_abilist_property_name, propBuf, "");
+ if (propBuf[0] == '\0') {
+ ALOGE("%s is not expected to be empty", cpu_abilist_property_name);
+ return -1;
+ }
+ snprintf(cpuAbiListBuf, sizeof(cpuAbiListBuf), "--cpu-abilist=%s", propBuf);
+ addOption(cpuAbiListBuf);
+
initArgs.version = JNI_VERSION_1_4;
initArgs.options = mOptions.editArray();
initArgs.nOptions = mOptions.size();
@@ -824,13 +887,10 @@
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
ALOGE("JNI_CreateJavaVM failed\n");
- goto bail;
+ return -1;
}
- result = 0;
-
-bail:
- return result;
+ return 0;
}
char* AndroidRuntime::toSlashClassName(const char* className)
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 3c1993e..9307ff9 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -33,6 +33,7 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <inttypes.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -173,7 +174,11 @@
static install_status_t
copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
{
- jstring* javaNativeLibPath = (jstring*) arg;
+ void** args = reinterpret_cast<void**>(arg);
+ jstring* javaNativeLibPath = (jstring*) args[0];
+ jboolean extractNativeLibs = *(jboolean*) args[1];
+ jboolean hasNativeBridge = *(jboolean*) args[2];
+
ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
size_t uncompLen;
@@ -181,13 +186,31 @@
long crc;
time_t modTime;
- if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, &when, &crc)) {
+ int method;
+ off64_t offset;
+
+ if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) {
ALOGD("Couldn't read zip entry info\n");
return INSTALL_FAILED_INVALID_APK;
- } else {
- struct tm t;
- ZipUtils::zipTimeToTimespec(when, &t);
- modTime = mktime(&t);
+ }
+
+ if (!extractNativeLibs) {
+ // check if library is uncompressed and page-aligned
+ if (method != ZipFileRO::kCompressStored) {
+ ALOGD("Library '%s' is compressed - will not be able to open it directly from apk.\n",
+ fileName);
+ return INSTALL_FAILED_INVALID_APK;
+ }
+
+ if (offset % PAGE_SIZE != 0) {
+ ALOGD("Library '%s' is not page-aligned - will not be able to open it directly from"
+ " apk.\n", fileName);
+ return INSTALL_FAILED_INVALID_APK;
+ }
+
+ if (!hasNativeBridge) {
+ return INSTALL_SUCCEEDED;
+ }
}
// Build local file path
@@ -208,6 +231,9 @@
}
// Only copy out the native file if it's different.
+ struct tm t;
+ ZipUtils::zipTimeToTimespec(when, &t);
+ modTime = mktime(&t);
struct stat64 st;
if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) {
return INSTALL_SUCCEEDED;
@@ -465,10 +491,12 @@
static jint
com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
- jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi)
+ jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
+ jboolean extractNativeLibs, jboolean hasNativeBridge)
{
+ void* args[] = { &javaNativeLibPath, &extractNativeLibs, &hasNativeBridge };
return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi,
- copyFileIfChanged, &javaNativeLibPath);
+ copyFileIfChanged, reinterpret_cast<void*>(args));
}
static jlong
@@ -548,7 +576,7 @@
"(J)V",
(void *)com_android_internal_content_NativeLibraryHelper_close},
{"nativeCopyNativeBinaries",
- "(JLjava/lang/String;Ljava/lang/String;)I",
+ "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
(void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
{"nativeSumNativeBinaries",
"(JLjava/lang/String;)J",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ccdb5db..0ded6d32 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3094,9 +3094,9 @@
</intent-filter>
</receiver>
- <receiver android:name="com.android.server.updates.TZInfoInstallReceiver" >
+ <receiver android:name="com.android.server.updates.TzDataInstallReceiver" >
<intent-filter>
- <action android:name="android.intent.action.UPDATE_TZINFO" />
+ <action android:name="android.intent.action.UPDATE_TZDATA" />
<data android:scheme="content" android:host="*" android:mimeType="*/*" />
</intent-filter>
</receiver>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index ea592cf..f69772a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1021,6 +1021,10 @@
<p>The default value of this attribute is <code>false</code>. -->
<attr name="resumeWhilePausing" format="boolean" />
+ <!-- When set installer will extract native libraries. If set to false
+ libraries in the apk must be stored and page-aligned. -->
+ <attr name="extractNativeLibs" format="boolean"/>
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1151,8 +1155,8 @@
@hide -->
<attr name="usesCleartextTraffic" />
<attr name="multiArch" />
+ <attr name="extractNativeLibs" />
</declare-styleable>
-
<!-- The <code>permission</code> tag declares a security permission that can be
used to control access from other packages to specific components or
features in your package (or other packages). See the
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2bb9aa8..bfd0d26 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2598,4 +2598,5 @@
<public type="style" name="Theme.DeviceDefault.Dialog.Alert" />
<public type="style" name="Theme.DeviceDefault.Light.Dialog.Alert" />
+ <public type="attr" name="extractNativeLibs" />
</resources>
diff --git a/core/tests/coretests/apks/install_jni_lib/Android.mk b/core/tests/coretests/apks/install_jni_lib/Android.mk
index b61ea8e..7322e8d 100644
--- a/core/tests/coretests/apks/install_jni_lib/Android.mk
+++ b/core/tests/coretests/apks/install_jni_lib/Android.mk
@@ -23,6 +23,14 @@
libnativehelper
LOCAL_MODULE := libframeworks_coretests_jni
+
+# this does not prevent build system
+# from installing library to /system/lib
LOCAL_MODULE_TAGS := tests
+# .. we want to avoid that... so we put it somewhere
+# bionic linker cant find it without outside help (nativetests):
+LOCAL_MODULE_PATH_32 := $($(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
+LOCAL_MODULE_PATH_64 := $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
+
include $(BUILD_SHARED_LIBRARY)
diff --git a/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp b/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp
index 957fc4a..e0b616c 100644
--- a/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp
+++ b/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp
@@ -27,8 +27,8 @@
{ "checkFunction", "()I", (void*) checkFunction },
};
-int register_com_android_framework_coretests_JNITests(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/framework/coretests/JNITests", sMethods,
+int register_com_android_frameworks_coretests_JNITests(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/frameworks/coretests/JNITests", sMethods,
NELEM(sMethods));
}
@@ -46,7 +46,7 @@
return JNI_ERR;
}
- if ((status = android::register_com_android_framework_coretests_JNITests(e)) < 0) {
+ if ((status = android::register_com_android_frameworks_coretests_JNITests(e)) < 0) {
return JNI_ERR;
}
diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.mk b/core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.mk
new file mode 100644
index 0000000..5fa2405
--- /dev/null
+++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := install_jni_lib_open_from_apk
+
+LOCAL_JNI_SHARED_LIBRARIES_ZIP_OPTIONS := -0
+LOCAL_PAGE_ALIGN_JNI_SHARED_LIBRARIES := true
+
+include $(FrameworkCoreTests_BUILD_PACKAGE)
diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/AndroidManifest.xml b/core/tests/coretests/apks/install_jni_lib_open_from_apk/AndroidManifest.xml
new file mode 100644
index 0000000..190f894
--- /dev/null
+++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_jni_lib_open_from_apk">
+
+ <application android:hasCode="true" android:label="@string/app_name" android:extractNativeLibs="false">
+ <activity android:name="com.android.frameworks.coretests.OpenFromApkActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/res/values/strings.xml b/core/tests/coretests/apks/install_jni_lib_open_from_apk/res/values/strings.xml
new file mode 100644
index 0000000..8c2a0bf
--- /dev/null
+++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Load From Apk Test</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/JNITests.java b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/JNITests.java
new file mode 100644
index 0000000..4f9176c
--- /dev/null
+++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/JNITests.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.frameworks.coretests;
+
+public class JNITests {
+ static {
+ System.loadLibrary("frameworks_coretests_jni");
+ }
+
+ public static native int checkFunction();
+}
diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/OpenFromApkActivity.java b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/OpenFromApkActivity.java
new file mode 100644
index 0000000..524cad7c
--- /dev/null
+++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/OpenFromApkActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.frameworks.coretests;
+
+import android.app.Activity;
+import android.widget.TextView;
+import android.os.Bundle;
+
+public class OpenFromApkActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView tv = new TextView(this);
+
+ int i = JNITests.checkFunction();
+
+ tv.setText("All is well: i=" + i);
+
+ setContentView(tv);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index cd45017..6fa28b1 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -804,4 +804,56 @@
assertFalse(Uri.parse("content://com.example/path/path").isPathPrefixMatch(
Uri.parse("content://com.example/path%2Fpath")));
}
+
+ public void testToSafeString() {
+ checkToSafeString("tel:xxxxxx", "tel:Google");
+ checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890");
+ checkToSafeString("tEl:xxx.xxx-xxxx", "tEl:123.456-7890");
+
+ checkToSafeString("sms:xxxxxx", "sms:123abc");
+ checkToSafeString("smS:xxx.xxx-xxxx", "smS:123.456-7890");
+
+ checkToSafeString("smsto:xxxxxx", "smsto:123abc");
+ checkToSafeString("SMSTo:xxx.xxx-xxxx", "SMSTo:123.456-7890");
+
+ checkToSafeString("mailto:xxxxxxx@xxxxxxx.xxx", "mailto:android@android.com");
+ checkToSafeString("Mailto:xxxxxxx@xxxxxxx.xxxxxxxxxx",
+ "Mailto:android@android.com/secret");
+
+ checkToSafeString("sip:xxxxxxx@xxxxxxx.xxxxxxxx", "sip:android@android.com:1234");
+ checkToSafeString("sIp:xxxxxxx@xxxxxxx.xxx", "sIp:android@android.com");
+
+ checkToSafeString("http://www.android.com/...", "http://www.android.com");
+ checkToSafeString("HTTP://www.android.com/...", "HTTP://www.android.com");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...",
+ "http://user:pwd@www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...",
+ "http://user@www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param");
+ checkToSafeString("http:///...", "http:///path?param");
+ checkToSafeString("http:///...", "http://");
+ checkToSafeString("http://:12345/...", "http://:12345/");
+
+ checkToSafeString("https://www.android.com/...", "https://www.android.com/secretUrl?param");
+ checkToSafeString("https://www.android.com:8443/...",
+ "https://user:pwd@www.android.com:8443/secretUrl?param");
+ checkToSafeString("https://www.android.com/...", "https://user:pwd@www.android.com");
+ checkToSafeString("Https://www.android.com/...", "Https://user:pwd@www.android.com");
+
+ checkToSafeString("ftp://ftp.android.com/...", "ftp://ftp.android.com/");
+ checkToSafeString("ftP://ftp.android.com/...", "ftP://anonymous@ftp.android.com/");
+ checkToSafeString("ftp://ftp.android.com:2121/...",
+ "ftp://root:love@ftp.android.com:2121/");
+
+ checkToSafeString("unsupported://ajkakjah/askdha/secret?secret",
+ "unsupported://ajkakjah/askdha/secret?secret");
+ checkToSafeString("unsupported:ajkakjah/askdha/secret?secret",
+ "unsupported:ajkakjah/askdha/secret?secret");
+ }
+
+ private void checkToSafeString(String expectedSafeString, String original) {
+ assertEquals(expectedSafeString, Uri.parse(original).toSafeString());
+ }
}
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index f3eb317..f72c7acd 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -457,7 +457,7 @@
String keyAlgorithmString = key.getAlgorithm();
@KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm;
- @KeyStoreKeyConstraints.AlgorithmEnum Integer digest;
+ @KeyStoreKeyConstraints.DigestEnum Integer digest;
try {
keyAlgorithm =
KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString);
@@ -466,87 +466,94 @@
throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
}
- if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) {
- throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString
- + ", parameter spec: "
- + KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm()));
- }
-
KeymasterArguments args = new KeymasterArguments();
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm));
- if (digest != null) {
- // Digest available from JCA key algorithm
- if (params.getDigest() != null) {
- // Digest also specified in parameters -- check that these two match
- if (digest != params.getDigest()) {
- throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString
+ @KeyStoreKeyConstraints.DigestEnum int digests;
+ if (params.isDigestsSpecified()) {
+ // Digest(s) specified in parameters
+ if (digest != null) {
+ // Digest also specified in the JCA key algorithm name.
+ if ((params.getDigests() & digest) != digest) {
+ throw new KeyStoreException("Key digest mismatch"
+ + ". Key: " + keyAlgorithmString
+ ", parameter spec: "
- + KeyStoreKeyConstraints.Digest.toString(params.getDigest()));
+ + KeyStoreKeyConstraints.Digest.allToString(params.getDigests()));
}
}
+ digests = params.getDigests();
} else {
- // Digest not available from JCA key algorithm
- digest = params.getDigest();
+ // No digest specified in parameters
+ if (digest != null) {
+ // Digest specified in the JCA key algorithm name.
+ digests = digest;
+ } else {
+ digests = 0;
+ }
}
- if (digest != null) {
- args.addInt(KeymasterDefs.KM_TAG_DIGEST,
- KeyStoreKeyConstraints.Digest.toKeymaster(digest));
+ for (int keymasterDigest : KeyStoreKeyConstraints.Digest.allToKeymaster(digests)) {
+ args.addInt(KeymasterDefs.KM_TAG_DIGEST, keymasterDigest);
+ }
+ if (digests != 0) {
+ // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+ // This code will blow up if mode than one digest is specified.
+ Integer digestOutputSizeBytes =
+ KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
+ if (digestOutputSizeBytes != null) {
+ // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
+ args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
+ }
+ }
+ if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+ if (digests == 0) {
+ throw new KeyStoreException("At least one digest algorithm must be specified"
+ + " for key algorithm " + keyAlgorithmString);
+ }
}
- @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
- ? params.getPurposes()
- : (KeyStoreKeyConstraints.Purpose.ENCRYPT
- | KeyStoreKeyConstraints.Purpose.DECRYPT
- | KeyStoreKeyConstraints.Purpose.SIGN
- | KeyStoreKeyConstraints.Purpose.VERIFY);
- for (int keymasterPurpose :
- KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
+ int purposes = params.getPurposes();
+ for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- if (params.getBlockMode() != null) {
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
- KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode()));
+ for (int keymasterBlockMode :
+ KeyStoreKeyConstraints.BlockMode.allToKeymaster(params.getBlockModes())) {
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
- if (params.getPadding() != null) {
- args.addInt(KeymasterDefs.KM_TAG_PADDING,
- KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding()));
+ for (int keymasterPadding :
+ KeyStoreKeyConstraints.Padding.allToKeymaster(params.getPaddings())) {
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
}
- if (params.getMaxUsesPerBoot() != null) {
- args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot());
- }
- if (params.getMinSecondsBetweenOperations() != null) {
- args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
- params.getMinSecondsBetweenOperations());
- }
- if (params.getUserAuthenticators().isEmpty()) {
+ if (params.getUserAuthenticators() == 0) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
} else {
- // TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized
-// for (int userAuthenticatorId : params.getUserAuthenticators()) {
-// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId);
-// }
+ args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
+ KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster(
+ params.getUserAuthenticators()));
}
- if (params.getUserAuthenticationValidityDurationSeconds() != null) {
+ if (params.getUserAuthenticationValidityDurationSeconds() != -1) {
args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
params.getUserAuthenticationValidityDurationSeconds());
}
- if (params.getKeyValidityStart() != null) {
- args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart());
- }
- if (params.getKeyValidityForOriginationEnd() != null) {
- args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
- params.getKeyValidityForOriginationEnd());
- }
- if (params.getKeyValidityForConsumptionEnd() != null) {
- args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
- params.getKeyValidityForConsumptionEnd());
- }
+ args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ (params.getKeyValidityStart() != null)
+ ? params.getKeyValidityStart() : new Date(0));
+ args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ (params.getKeyValidityForOriginationEnd() != null)
+ ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ (params.getKeyValidityForConsumptionEnd() != null)
+ ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
// TODO: Remove this once keymaster does not require us to specify the size of imported key.
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
+ // Permit caller-specified IV. This is needed for the Cipher abstraction.
+ args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
+ }
+
Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias);
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias;
int errorCode = mKeyStore.importKey(
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 9081e92..635b2fa 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -18,6 +18,9 @@
import java.security.Provider;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+
/**
* A provider focused on providing JCA interfaces for the Android KeyStore.
*
@@ -26,14 +29,104 @@
public class AndroidKeyStoreProvider extends Provider {
public static final String PROVIDER_NAME = "AndroidKeyStore";
+ // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
+ // classes when this provider is instantiated and installed early on during each app's
+ // initialization process.
+
+ private static final String PACKAGE_NAME = "android.security";
+ private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
+ PACKAGE_NAME + ".KeyStoreSecretKey";
+
public AndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
// java.security.KeyStore
- put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName());
+ put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStore");
// java.security.KeyPairGenerator
- put("KeyPairGenerator.EC", AndroidKeyPairGenerator.EC.class.getName());
- put("KeyPairGenerator.RSA", AndroidKeyPairGenerator.RSA.class.getName());
+ put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGenerator$EC");
+ put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGenerator$RSA");
+
+ // javax.crypto.KeyGenerator
+ put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES");
+ put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1");
+ put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224");
+ put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256");
+ put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384");
+ put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512");
+
+ // java.security.SecretKeyFactory
+ putSecretKeyFactoryImpl("AES");
+ putSecretKeyFactoryImpl("HmacSHA1");
+ putSecretKeyFactoryImpl("HmacSHA224");
+ putSecretKeyFactoryImpl("HmacSHA256");
+ putSecretKeyFactoryImpl("HmacSHA384");
+ putSecretKeyFactoryImpl("HmacSHA512");
+
+ // javax.crypto.Mac
+ putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224");
+ putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256");
+ putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384");
+ putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512");
+
+ // javax.crypto.Cipher
+ putSymmetricCipherImpl("AES/ECB/NoPadding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$NoPadding");
+ putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$PKCS7Padding");
+
+ putSymmetricCipherImpl("AES/CBC/NoPadding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$NoPadding");
+ putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$PKCS7Padding");
+
+ putSymmetricCipherImpl("AES/CTR/NoPadding",
+ PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding");
+ }
+
+ private void putSecretKeyFactoryImpl(String algorithm) {
+ put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi");
+ }
+
+ private void putMacImpl(String algorithm, String implClass) {
+ put("Mac." + algorithm, implClass);
+ put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
+ }
+
+ private void putSymmetricCipherImpl(String transformation, String implClass) {
+ put("Cipher." + transformation, implClass);
+ put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
+ }
+
+ /**
+ * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+ * primitive.
+ *
+ * <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
+ *
+ * @return KeyStore operation handle or {@code null} if the provided primitive's KeyStore
+ * operation is not in progress.
+ *
+ * @throws IllegalArgumentException if the provided primitive is not supported or is not backed
+ * by AndroidKeyStore provider.
+ */
+ public static Long getKeyStoreOperationHandle(Object cryptoPrimitive) {
+ if (cryptoPrimitive == null) {
+ throw new NullPointerException();
+ }
+ Object spi;
+ if (cryptoPrimitive instanceof Mac) {
+ spi = ((Mac) cryptoPrimitive).getSpi();
+ } else if (cryptoPrimitive instanceof Cipher) {
+ spi = ((Cipher) cryptoPrimitive).getSpi();
+ } else {
+ throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive);
+ }
+ if (!(spi instanceof KeyStoreCryptoOperation)) {
+ throw new IllegalArgumentException(
+ "Crypto primitive not backed by AndroidKeyStore: " + cryptoPrimitive
+ + ", spi: " + spi);
+ }
+ return ((KeyStoreCryptoOperation) spi).getOperationHandle();
}
}
diff --git a/keystore/java/android/security/CryptoOperationException.java b/keystore/java/android/security/CryptoOperationException.java
new file mode 100644
index 0000000..00c142f
--- /dev/null
+++ b/keystore/java/android/security/CryptoOperationException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * Base class for exceptions during cryptographic operations which cannot throw a suitable checked
+ * exception.
+ *
+ * <p>The contract of the majority of crypto primitives/operations (e.g. {@code Cipher} or
+ * {@code Signature}) is that they can throw a checked exception during initialization, but are not
+ * permitted to throw a checked exception during operation. Because crypto operations can fail
+ * for a variety of reasons after initialization, this base class provides type-safety for unchecked
+ * exceptions that may be thrown in those cases.
+ *
+ * @hide
+ */
+public class CryptoOperationException extends RuntimeException {
+
+ /**
+ * Constructs a new {@code CryptoOperationException} without detail message and cause.
+ */
+ public CryptoOperationException() {
+ super();
+ }
+
+ /**
+ * Constructs a new {@code CryptoOperationException} with the provided detail message and no
+ * cause.
+ */
+ public CryptoOperationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code CryptoOperationException} with the provided detail message and cause.
+ */
+ public CryptoOperationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new {@code CryptoOperationException} with the provided cause.
+ */
+ public CryptoOperationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/keystore/java/android/security/KeyExpiredException.java b/keystore/java/android/security/KeyExpiredException.java
new file mode 100644
index 0000000..35a5acc
--- /dev/null
+++ b/keystore/java/android/security/KeyExpiredException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * Indicates that a cryptographic operation failed because the employed key's validity end date
+ * is in the past.
+ *
+ * @hide
+ */
+public class KeyExpiredException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code KeyExpiredException} without detail message and cause.
+ */
+ public KeyExpiredException() {
+ super("Key expired");
+ }
+
+ /**
+ * Constructs a new {@code KeyExpiredException} with the provided detail message and no cause.
+ */
+ public KeyExpiredException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code KeyExpiredException} with the provided detail message and cause.
+ */
+ public KeyExpiredException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
new file mode 100644
index 0000000..3ada7f6
--- /dev/null
+++ b/keystore/java/android/security/KeyGeneratorSpec.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Date;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+/**
+ * {@link AlgorithmParameterSpec} for initializing a {@code KeyGenerator} that works with
+ * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>.
+ *
+ * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API using the
+ * {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up some UI to
+ * ask the user to unlock or initialize the Android KeyStore facility.
+ *
+ * <p>After generation, the {@code keyStoreAlias} is used with the
+ * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)}
+ * interface to retrieve the {@link SecretKey}.
+ *
+ * @hide
+ */
+public class KeyGeneratorSpec implements AlgorithmParameterSpec {
+
+ private final Context mContext;
+ private final String mKeystoreAlias;
+ private final int mFlags;
+ private final Integer mKeySize;
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private final int mUserAuthenticationValidityDurationSeconds;
+
+ private KeyGeneratorSpec(
+ Context context,
+ String keyStoreAlias,
+ int flags,
+ Integer keySize,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ int userAuthenticationValidityDurationSeconds) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ } else if (TextUtils.isEmpty(keyStoreAlias)) {
+ throw new IllegalArgumentException("keyStoreAlias must not be empty");
+ } else if ((userAuthenticationValidityDurationSeconds < 0)
+ && (userAuthenticationValidityDurationSeconds != -1)) {
+ throw new IllegalArgumentException(
+ "userAuthenticationValidityDurationSeconds must not be negative");
+ }
+
+ mContext = context;
+ mKeystoreAlias = keyStoreAlias;
+ mFlags = flags;
+ mKeySize = keySize;
+ mKeyValidityStart = keyValidityStart;
+ mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
+ mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mPurposes = purposes;
+ mPaddings = paddings;
+ mBlockModes = blockModes;
+ mUserAuthenticators = userAuthenticators;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * 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}.
+ */
+ public String getKeystoreAlias() {
+ return mKeystoreAlias;
+ }
+
+ /**
+ * @hide
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Gets the requested key size or {@code null} if the default size should be used.
+ */
+ public Integer getKeySize() {
+ return mKeySize;
+ }
+
+ /**
+ * Gets the time instant before which the key is not yet valid.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityStart() {
+ return mKeyValidityStart;
+ }
+
+ /**
+ * Gets the time instant after which the key is no longer valid for decryption and verification.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityForConsumptionEnd() {
+ return mKeyValidityForConsumptionEnd;
+ }
+
+ /**
+ * Gets the time instant after which the key is no longer valid for encryption and signing.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityForOriginationEnd() {
+ return mKeyValidityForOriginationEnd;
+ }
+
+ /**
+ * Gets the set of purposes for which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the set of padding schemes to which the key is restricted.
+ */
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
+ }
+
+ /**
+ * Gets the set of block modes to which the key is restricted.
+ */
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
+ }
+
+ /**
+ * Gets the set of user authenticators which protect access to this key. The key can only be
+ * used iff the user has authenticated to at least one of these user authenticators.
+ *
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which this key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Returns {@code true} if the key must be encrypted in the {@link java.security.KeyStore}.
+ */
+ public boolean isEncryptionRequired() {
+ return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0;
+ }
+
+ public static class Builder {
+ private final Context mContext;
+ private String mKeystoreAlias;
+ private int mFlags;
+ private Integer mKeySize;
+ private Date mKeyValidityStart;
+ private Date mKeyValidityForOriginationEnd;
+ private Date mKeyValidityForConsumptionEnd;
+ private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+
+ /**
+ * Creates a new instance of the {@code Builder} with the given {@code context}. The
+ * {@code context} passed in may be used to pop up some UI to ask the user to unlock or
+ * initialize the Android KeyStore facility.
+ */
+ public Builder(Context context) {
+ if (context == null) {
+ throw new NullPointerException("context == null");
+ }
+ mContext = context;
+ }
+
+ /**
+ * Sets the alias to be used to retrieve the key later from a {@link java.security.KeyStore}
+ * instance using the {@code AndroidKeyStore} provider.
+ *
+ * <p>The alias must be provided. There is no default.
+ */
+ public Builder setAlias(String alias) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+ mKeystoreAlias = alias;
+ return this;
+ }
+
+ /**
+ * Sets the size (in bits) of the key to be generated.
+ *
+ * <p>By default, the key size will be determines based on the key algorithm. For example,
+ * for {@code HmacSHA256}, the key size will default to {@code 256}.
+ */
+ public Builder setKeySize(int keySize) {
+ mKeySize = keySize;
+ return this;
+ }
+
+ /**
+ * Indicates that this key must be encrypted at rest on storage. Note that enabling this
+ * will require that the user enable a strong lock screen (e.g., PIN, password) before
+ * creating or using the generated key is successful.
+ */
+ public Builder setEncryptionRequired(boolean required) {
+ if (required) {
+ mFlags |= KeyStore.FLAG_ENCRYPTED;
+ } else {
+ mFlags &= ~KeyStore.FLAG_ENCRYPTED;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the time instant before which the key is not yet valid.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityEnd(Date)
+ */
+ public Builder setKeyValidityStart(Date startDate) {
+ mKeyValidityStart = startDate;
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityStart(Date)
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ * @see #setKeyValidityForOriginationEnd(Date)
+ */
+ public Builder setKeyValidityEnd(Date endDate) {
+ setKeyValidityForOriginationEnd(endDate);
+ setKeyValidityForConsumptionEnd(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for encryption and signing.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ */
+ public Builder setKeyValidityForOriginationEnd(Date endDate) {
+ mKeyValidityForOriginationEnd = endDate;
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for decryption and
+ * verification.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForOriginationEnd(Date)
+ */
+ public Builder setKeyValidityForConsumptionEnd(Date endDate) {
+ mKeyValidityForConsumptionEnd = endDate;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only for the provided set of purposes.
+ *
+ * <p>This restriction must be specified. There is no default.
+ */
+ public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) {
+ mPurposes = purposes;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided padding schemes. Attempts to use
+ * the key with any other padding will be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ */
+ public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+ mPaddings = paddings;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided block modes. Attempts to use the
+ * key with any other block modes will be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ */
+ public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+ mBlockModes = blockModes;
+ return this;
+ }
+
+ /**
+ * Sets the user authenticators which protect access to this key. The key can only be used
+ * iff the user has authenticated to at least one of these user authenticators.
+ *
+ * <p>By default, the key can be used without user authentication.
+ *
+ * @param userAuthenticators user authenticators or empty list if this key can be accessed
+ * without user authentication.
+ *
+ * @see #setUserAuthenticationValidityDurationSeconds(int)
+ */
+ public Builder setUserAuthenticators(
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+ mUserAuthenticators = userAuthenticators;
+ return this;
+ }
+
+ /**
+ * Sets the duration of time (seconds) for which this key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * <p>By default, the user needs to authenticate for every use of the key.
+ *
+ * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
+ * every use of the key.
+ *
+ * @see #setUserAuthenticators(int)
+ */
+ public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
+ mUserAuthenticationValidityDurationSeconds = seconds;
+ return this;
+ }
+
+ /**
+ * Builds a new instance instance of {@code KeyGeneratorSpec}.
+ *
+ * @throws IllegalArgumentException if a required field is missing or violates a constraint.
+ */
+ public KeyGeneratorSpec build() {
+ return new KeyGeneratorSpec(mContext,
+ mKeystoreAlias,
+ mFlags,
+ mKeySize,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mPaddings,
+ mBlockModes,
+ mUserAuthenticators,
+ mUserAuthenticationValidityDurationSeconds);
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyNotYetValidException.java b/keystore/java/android/security/KeyNotYetValidException.java
new file mode 100644
index 0000000..f1c2cac
--- /dev/null
+++ b/keystore/java/android/security/KeyNotYetValidException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * Indicates that a cryptographic operation failed because the employed key's validity start date
+ * is in the future.
+ *
+ * @hide
+ */
+public class KeyNotYetValidException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code KeyNotYetValidException} without detail message and cause.
+ */
+ public KeyNotYetValidException() {
+ super("Key not yet valid");
+ }
+
+ /**
+ * Constructs a new {@code KeyNotYetValidException} with the provided detail message and no
+ * cause.
+ */
+ public KeyNotYetValidException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code KeyNotYetValidException} with the provided detail message and cause.
+ */
+ public KeyNotYetValidException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index cc097aa..edaa9a54 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -72,6 +72,24 @@
private final int mFlags;
+ private final Date mKeyValidityStart;
+
+ private final Date mKeyValidityForOriginationEnd;
+
+ private final Date mKeyValidityForConsumptionEnd;
+
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+
+ private final @KeyStoreKeyConstraints.DigestEnum int mDigests;
+
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+
+ private final int mUserAuthenticationValidityDurationSeconds;
+
/**
* Parameter specification for the "{@code AndroidKeyPairGenerator}"
* instance of the {@link java.security.KeyPairGenerator} API. The
@@ -106,7 +124,16 @@
*/
public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
- Date startDate, Date endDate, int flags) {
+ Date startDate, Date endDate, int flags,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.DigestEnum int digests,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ int userAuthenticationValidityDurationSeconds) {
if (context == null) {
throw new IllegalArgumentException("context == null");
} else if (TextUtils.isEmpty(keyStoreAlias)) {
@@ -121,6 +148,10 @@
throw new IllegalArgumentException("endDate == null");
} else if (endDate.before(startDate)) {
throw new IllegalArgumentException("endDate < startDate");
+ } else if ((userAuthenticationValidityDurationSeconds < 0)
+ && (userAuthenticationValidityDurationSeconds != -1)) {
+ throw new IllegalArgumentException(
+ "userAuthenticationValidityDurationSeconds must not be negative");
}
mContext = context;
@@ -133,6 +164,26 @@
mStartDate = startDate;
mEndDate = endDate;
mFlags = flags;
+ mKeyValidityStart = keyValidityStart;
+ mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
+ mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mPurposes = purposes;
+ mDigests = digests;
+ mPaddings = paddings;
+ mBlockModes = blockModes;
+ mUserAuthenticators = userAuthenticators;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * TODO: Remove this constructor once tests are switched over to the new one above.
+ * @hide
+ */
+ public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
+ AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
+ Date startDate, Date endDate, int flags) {
+ this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate,
+ endDate, flags, startDate, endDate, endDate, 0, 0, 0, 0, 0, -1);
}
/**
@@ -222,6 +273,107 @@
}
/**
+ * Gets the time instant before which the key pair is not yet valid.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityStart() {
+ return mKeyValidityStart;
+ }
+
+ /**
+ * Gets the time instant after which the key pair is no longer valid for decryption and
+ * verification.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityForConsumptionEnd() {
+ return mKeyValidityForConsumptionEnd;
+ }
+
+ /**
+ * Gets the time instant after which the key pair is no longer valid for encryption and signing.
+ *
+ * @return instant or {@code null} if not restricted.
+ *
+ * @hide
+ */
+ public Date getKeyValidityForOriginationEnd() {
+ return mKeyValidityForOriginationEnd;
+ }
+
+ /**
+ * Gets the set of purposes for which the key can be used.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the set of digests to which the key is restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+ return mDigests;
+ }
+
+ /**
+ * Gets the set of padding schemes to which the key is restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
+ }
+
+ /**
+ * Gets the set of block modes to which the key is restricted.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
+ }
+
+ /**
+ * Gets the set of user authenticators which protect access to the private key. The key can only
+ * be used iff the user has authenticated to at least one of these user authenticators.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
+ *
+ * @hide
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which the private key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
+ *
+ * @hide
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+
+ /**
* Builder class for {@link KeyPairGeneratorSpec} objects.
* <p>
* This will build a parameter spec for use with the <a href="{@docRoot}
@@ -263,6 +415,24 @@
private int mFlags;
+ private Date mKeyValidityStart;
+
+ private Date mKeyValidityForOriginationEnd;
+
+ private Date mKeyValidityForConsumptionEnd;
+
+ private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+
+ private @KeyStoreKeyConstraints.DigestEnum int mDigests;
+
+ private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+
+ private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+
+ private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+
+ private int mUserAuthenticationValidityDurationSeconds = -1;
+
/**
* Creates a new instance of the {@code Builder} with the given
* {@code context}. The {@code context} passed in may be used to pop up
@@ -389,14 +559,185 @@
}
/**
+ * Sets the time instant before which the key is not yet valid.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityStart(Date startDate) {
+ mKeyValidityStart = startDate;
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityStart(Date)
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ * @see #setKeyValidityForOriginationEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityEnd(Date endDate) {
+ setKeyValidityForOriginationEnd(endDate);
+ setKeyValidityForConsumptionEnd(endDate);
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for encryption and signing.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForConsumptionEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityForOriginationEnd(Date endDate) {
+ mKeyValidityForOriginationEnd = endDate;
+ return this;
+ }
+
+ /**
+ * Sets the time instant after which the key is no longer valid for decryption and
+ * verification.
+ *
+ * <b>By default, the key is valid at any instant.
+ *
+ * @see #setKeyValidityForOriginationEnd(Date)
+ *
+ * @hide
+ */
+ public Builder setKeyValidityForConsumptionEnd(Date endDate) {
+ mKeyValidityForConsumptionEnd = endDate;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only for the provided set of purposes.
+ *
+ * <p>This restriction must be specified. There is no default.
+ *
+ * @hide
+ */
+ public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) {
+ mPurposes = purposes;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided digests. Attempts to use the key
+ * with any other digests be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for signing/verification.
+ *
+ * @hide
+ */
+ public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) {
+ mDigests = digests;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided padding schemes. Attempts to use
+ * the key with any other padding will be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ *
+ * @hide
+ */
+ public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+ mPaddings = paddings;
+ return this;
+ }
+
+ /**
+ * Restricts the key to being used only with the provided block mode when encrypting or
+ * decrypting. Attempts to use the key with any other block modes will be rejected.
+ *
+ * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ *
+ * @hide
+ */
+ public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+ mBlockModes = blockModes;
+ return this;
+ }
+
+ /**
+ * Sets the user authenticators which protect access to this key. The key can only be used
+ * iff the user has authenticated to at least one of these user authenticators.
+ *
+ * <p>By default, the key can be used without user authentication.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @param userAuthenticators user authenticators or {@code 0} if this key can be accessed
+ * without user authentication.
+ *
+ * @see #setUserAuthenticationValidityDurationSeconds(int)
+ *
+ * @hide
+ */
+ public Builder setUserAuthenticators(
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+ mUserAuthenticators = userAuthenticators;
+ return this;
+ }
+
+ /**
+ * Sets the duration of time (seconds) for which this key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * <p>By default, the user needs to authenticate for every use of the key.
+ *
+ * <p>This restriction applies only to private key operations. Public key operations are not
+ * restricted.
+ *
+ * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
+ * every use of the key.
+ *
+ * @see #setUserAuthenticators(int)
+ *
+ * @hide
+ */
+ public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
+ mUserAuthenticationValidityDurationSeconds = seconds;
+ return this;
+ }
+
+ /**
* Builds the instance of the {@code KeyPairGeneratorSpec}.
*
* @throws IllegalArgumentException if a required field is missing
* @return built instance of {@code KeyPairGeneratorSpec}
*/
public KeyPairGeneratorSpec build() {
- return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mKeyType, mKeySize, mSpec,
- mSubjectDN, mSerialNumber, mStartDate, mEndDate, mFlags);
+ return new KeyPairGeneratorSpec(mContext,
+ mKeystoreAlias,
+ mKeyType,
+ mKeySize,
+ mSpec,
+ mSubjectDN,
+ mSerialNumber,
+ mStartDate,
+ mEndDate,
+ mFlags,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mDigests,
+ mPaddings,
+ mBlockModes,
+ mUserAuthenticators,
+ mUserAuthenticationValidityDurationSeconds);
}
}
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index f68b3f6..84a664e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -26,6 +26,7 @@
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterBlob;
+import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.util.Log;
@@ -476,4 +477,87 @@
return SYSTEM_ERROR;
}
}
+
+ /**
+ * Check if the operation referenced by {@code token} is currently authorized.
+ *
+ * @param token An operation token returned by a call to {@link KeyStore.begin}.
+ */
+ public boolean isOperationAuthorized(IBinder token) {
+ try {
+ return mBinder.isOperationAuthorized(token);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
+ /**
+ * Add an authentication record to the keystore authorization table.
+ *
+ * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
+ * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to
+ * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
+ */
+ public int addAuthToken(byte[] authToken) {
+ try {
+ return mBinder.addAuthToken(authToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
+ public static KeyStoreException getKeyStoreException(int errorCode) {
+ if (errorCode > 0) {
+ // KeyStore layer error
+ switch (errorCode) {
+ case NO_ERROR:
+ return new KeyStoreException(errorCode, "OK");
+ case LOCKED:
+ return new KeyStoreException(errorCode, "Keystore locked");
+ case UNINITIALIZED:
+ return new KeyStoreException(errorCode, "Keystore not initialized");
+ case SYSTEM_ERROR:
+ return new KeyStoreException(errorCode, "System error");
+ case PERMISSION_DENIED:
+ return new KeyStoreException(errorCode, "Permission denied");
+ case KEY_NOT_FOUND:
+ return new KeyStoreException(errorCode, "Key not found");
+ case VALUE_CORRUPTED:
+ return new KeyStoreException(errorCode, "Key blob corrupted");
+ default:
+ return new KeyStoreException(errorCode, String.valueOf(errorCode));
+ }
+ } else {
+ // Keymaster layer error
+ switch (errorCode) {
+ case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
+ // The name of this parameter significantly differs between Keymaster and
+ // framework APIs. Use the framework wording to make life easier for developers.
+ return new KeyStoreException(errorCode,
+ "Invalid user authentication validity duration");
+ default:
+ return new KeyStoreException(errorCode,
+ KeymasterDefs.getErrorMessage(errorCode));
+ }
+ }
+ }
+
+ public static CryptoOperationException getCryptoOperationException(KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
+ return new KeyExpiredException();
+ case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
+ return new KeyNotYetValidException();
+ case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED:
+ return new UserNotAuthenticatedException();
+ default:
+ return new CryptoOperationException("Crypto operation failed", e);
+ }
+ }
+
+ public static CryptoOperationException getCryptoOperationException(int errorCode) {
+ return getCryptoOperationException(getKeyStoreException(errorCode));
+ }
}
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
new file mode 100644
index 0000000..487eac0
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.os.IBinder;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers.
+ *
+ * @hide
+ */
+public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCryptoOperation {
+
+ public abstract static class AES extends KeyStoreCipherSpi {
+ protected AES(@KeyStoreKeyConstraints.BlockModeEnum int blockMode,
+ @KeyStoreKeyConstraints.PaddingEnum int padding, boolean ivUsed) {
+ super(KeyStoreKeyConstraints.Algorithm.AES,
+ blockMode,
+ padding,
+ 16,
+ ivUsed);
+ }
+
+ public abstract static class ECB extends AES {
+ protected ECB(@KeyStoreKeyConstraints.PaddingEnum int padding) {
+ super(KeyStoreKeyConstraints.BlockMode.ECB, padding, false);
+ }
+
+ public static class NoPadding extends ECB {
+ public NoPadding() {
+ super(KeyStoreKeyConstraints.Padding.NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends ECB {
+ public PKCS7Padding() {
+ super(KeyStoreKeyConstraints.Padding.PKCS7);
+ }
+ }
+ }
+
+ public abstract static class CBC extends AES {
+ protected CBC(@KeyStoreKeyConstraints.BlockModeEnum int padding) {
+ super(KeyStoreKeyConstraints.BlockMode.CBC, padding, true);
+ }
+
+ public static class NoPadding extends CBC {
+ public NoPadding() {
+ super(KeyStoreKeyConstraints.Padding.NONE);
+ }
+ }
+
+ public static class PKCS7Padding extends CBC {
+ public PKCS7Padding() {
+ super(KeyStoreKeyConstraints.Padding.PKCS7);
+ }
+ }
+ }
+
+ public abstract static class CTR extends AES {
+ protected CTR(@KeyStoreKeyConstraints.BlockModeEnum int padding) {
+ super(KeyStoreKeyConstraints.BlockMode.CTR, padding, true);
+ }
+
+ public static class NoPadding extends CTR {
+ public NoPadding() {
+ super(KeyStoreKeyConstraints.Padding.NONE);
+ }
+ }
+ }
+ }
+
+ private final KeyStore mKeyStore;
+ private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPadding;
+ private final int mBlockSizeBytes;
+
+ /** Whether this transformation requires an IV. */
+ private final boolean mIvRequired;
+
+ // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
+ // doFinal finishes.
+ protected boolean mEncrypting;
+ private KeyStoreSecretKey mKey;
+ private SecureRandom mRng;
+ private boolean mFirstOperationInitiated;
+ private byte[] mIv;
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
+
+ // Fields below must be reset after doFinal
+ private byte[] mAdditionalEntropyForBegin;
+
+ /**
+ * Token referencing this operation inside keystore service. It is initialized by
+ * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some
+ * error conditions in between.
+ */
+ private IBinder mOperationToken;
+ private Long mOperationHandle;
+ private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
+
+ protected KeyStoreCipherSpi(
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockMode,
+ @KeyStoreKeyConstraints.PaddingEnum int padding,
+ int blockSizeBytes,
+ boolean ivUsed) {
+ mKeyStore = KeyStore.getInstance();
+ mAlgorithm = algorithm;
+ mBlockMode = blockMode;
+ mPadding = padding;
+ mBlockSizeBytes = blockSizeBytes;
+ mIvRequired = ivUsed;
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters();
+ ensureKeystoreOperationInitialized();
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
+ throws InvalidKeyException, InvalidAlgorithmParameterException {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ init(opmode, key, random);
+ initAlgorithmSpecificParameters(params);
+ ensureKeystoreOperationInitialized();
+ }
+
+ private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ resetAll();
+ if (!(key instanceof KeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+ }
+ mKey = (KeyStoreSecretKey) key;
+ mRng = random;
+ mIv = null;
+ mFirstOperationInitiated = false;
+
+ if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
+ throw new UnsupportedOperationException(
+ "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
+ }
+ mEncrypting = opmode == Cipher.ENCRYPT_MODE;
+ }
+
+ private void resetAll() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mEncrypting = false;
+ mKey = null;
+ mRng = null;
+ mFirstOperationInitiated = false;
+ mIv = null;
+ mIvHasBeenUsed = false;
+ mAdditionalEntropyForBegin = null;
+ mOperationToken = null;
+ mOperationHandle = null;
+ mMainDataStreamer = null;
+ }
+
+ private void resetWhilePreservingInitState() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mOperationHandle = null;
+ mMainDataStreamer = null;
+ mAdditionalEntropyForBegin = null;
+ }
+
+ private void ensureKeystoreOperationInitialized() {
+ if (mMainDataStreamer != null) {
+ return;
+ }
+ if (mKey == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) {
+ // IV is being reused for encryption: this violates security best practices.
+ throw new IllegalStateException(
+ "IV has already been used. Reusing IV in encryption mode violates security best"
+ + " practices.");
+ }
+
+ KeymasterArguments keymasterInputArgs = new KeymasterArguments();
+ keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm);
+ keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mBlockMode);
+ keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mPadding);
+ addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
+
+ KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
+ OperationResult opResult = mKeyStore.begin(
+ mKey.getAlias(),
+ mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
+ true, // permit aborting this operation if keystore runs out of resources
+ keymasterInputArgs,
+ mAdditionalEntropyForBegin,
+ keymasterOutputArgs);
+ mAdditionalEntropyForBegin = null;
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getCryptoOperationException(opResult.resultCode);
+ }
+
+ if (opResult.token == null) {
+ throw new CryptoOperationException("Keystore returned null operation token");
+ }
+ mOperationToken = opResult.token;
+ mOperationHandle = opResult.operationHandle;
+ loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
+ mFirstOperationInitiated = true;
+ mIvHasBeenUsed = true;
+ mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ mKeyStore, opResult.token));
+ }
+
+ @Override
+ protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+ ensureKeystoreOperationInitialized();
+
+ if (inputLen == 0) {
+ return null;
+ }
+
+ byte[] output;
+ try {
+ output = mMainDataStreamer.update(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ throw KeyStore.getCryptoOperationException(e);
+ }
+
+ if (output.length == 0) {
+ return null;
+ }
+
+ return output;
+ }
+
+ @Override
+ protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
+ int outputOffset) throws ShortBufferException {
+ byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
+ if (outputCopy == null) {
+ return 0;
+ }
+ int outputAvailable = output.length - outputOffset;
+ if (outputCopy.length > outputAvailable) {
+ throw new ShortBufferException("Output buffer too short. Produced: "
+ + outputCopy.length + ", available: " + outputAvailable);
+ }
+ System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
+ return outputCopy.length;
+ }
+
+ @Override
+ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
+ throws IllegalBlockSizeException, BadPaddingException {
+ ensureKeystoreOperationInitialized();
+
+ byte[] output;
+ try {
+ output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
+ } catch (KeyStoreException e) {
+ switch (e.getErrorCode()) {
+ case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
+ throw new IllegalBlockSizeException();
+ case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
+ throw new BadPaddingException();
+ case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
+ throw new AEADBadTagException();
+ default:
+ throw KeyStore.getCryptoOperationException(e);
+ }
+ }
+
+ resetWhilePreservingInitState();
+ return output;
+ }
+
+ @Override
+ protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+ int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
+ BadPaddingException {
+ byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
+ if (outputCopy == null) {
+ return 0;
+ }
+ int outputAvailable = output.length - outputOffset;
+ if (outputCopy.length > outputAvailable) {
+ throw new ShortBufferException("Output buffer too short. Produced: "
+ + outputCopy.length + ", available: " + outputAvailable);
+ }
+ System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
+ return outputCopy.length;
+ }
+
+ @Override
+ protected int engineGetBlockSize() {
+ return mBlockSizeBytes;
+ }
+
+ @Override
+ protected byte[] engineGetIV() {
+ return (mIv != null) ? mIv.clone() : null;
+ }
+
+ @Override
+ protected int engineGetOutputSize(int inputLen) {
+ return inputLen + 3 * engineGetBlockSize();
+ }
+
+ @Override
+ protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
+ // This should never be invoked because all algorithms registered with the AndroidKeyStore
+ // provide explicitly specify block mode.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void engineSetPadding(String arg0) throws NoSuchPaddingException {
+ // This should never be invoked because all algorithms registered with the AndroidKeyStore
+ // provide explicitly specify padding mode.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mKeyStore.abort(operationToken);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public Long getOperationHandle() {
+ return mOperationHandle;
+ }
+
+ // The methods below may need to be overridden by subclasses that use algorithm-specific
+ // parameters.
+
+ /**
+ * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null}
+ * if no algorithm-specific parameters are used.
+ *
+ * <p>This implementation only handles the IV parameter.
+ */
+ @Override
+ protected AlgorithmParameters engineGetParameters() {
+ if (!mIvRequired) {
+ return null;
+ }
+ if ((mIv != null) && (mIv.length > 0)) {
+ try {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
+ params.init(new IvParameterSpec(mIv));
+ return params;
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e);
+ } catch (InvalidParameterSpecException e) {
+ throw new RuntimeException(
+ "Failed to initialize AES AlgorithmParameters with an IV", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
+ * may need to be stored to be reused after {@code doFinal}.
+ *
+ * <p>The default implementation only handles the IV parameters.
+ *
+ * @param params algorithm parameters.
+ *
+ * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
+ * automatically configured and thus {@code Cipher.init} needs to be invoked with
+ * explicitly provided parameters.
+ */
+ protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ if (!mIvRequired) {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+ }
+ return;
+ }
+
+ // IV is used
+ if (params == null) {
+ if (!mEncrypting) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException(
+ "IvParameterSpec must be provided when decrypting");
+ }
+ return;
+ }
+ if (!(params instanceof IvParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
+ }
+ mIv = ((IvParameterSpec) params).getIV();
+ if (mIv == null) {
+ throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
+ }
+ }
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
+ * may need to be stored to be reused after {@code doFinal}.
+ *
+ * <p>The default implementation only handles the IV parameters.
+ *
+ * @param params algorithm parameters.
+ *
+ * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
+ * automatically configured and thus {@code Cipher.init} needs to be invoked with
+ * explicitly provided parameters.
+ */
+ protected void initAlgorithmSpecificParameters(AlgorithmParameters params)
+ throws InvalidAlgorithmParameterException {
+ if (!mIvRequired) {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+ }
+ return;
+ }
+
+ // IV is used
+ if (params == null) {
+ if (!mEncrypting) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ return;
+ }
+
+ IvParameterSpec ivSpec;
+ try {
+ ivSpec = params.getParameterSpec(IvParameterSpec.class);
+ } catch (InvalidParameterSpecException e) {
+ if (!mEncrypting) {
+ // IV must be provided by the caller
+ throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ + ", but not found in parameters: " + params, e);
+ }
+ mIv = null;
+ return;
+ }
+ mIv = ivSpec.getIV();
+ if (mIv == null) {
+ throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
+ }
+ }
+
+ /**
+ * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
+ * may need to be stored to be reused after {@code doFinal}.
+ *
+ * <p>The default implementation only handles the IV parameter.
+ *
+ * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured
+ * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters.
+ */
+ protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
+ if (!mIvRequired) {
+ return;
+ }
+
+ // IV is used
+ if (!mEncrypting) {
+ throw new InvalidKeyException("IV required when decrypting"
+ + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+ }
+ }
+
+ /**
+ * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
+ *
+ * <p>The default implementation takes care of the IV.
+ *
+ * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
+ * parameters.
+ */
+ protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
+ if (!mFirstOperationInitiated) {
+ // First begin operation -- see if we need to provide additional entropy for IV
+ // generation.
+ if (mIvRequired) {
+ // IV is needed
+ if ((mIv == null) && (mEncrypting)) {
+ // TODO: Switch to keymaster-generated IV code below once keymaster supports
+ // that.
+ // IV is needed but was not provided by the caller -- generate an IV.
+ mIv = new byte[mBlockSizeBytes];
+ SecureRandom rng = (mRng != null) ? mRng : new SecureRandom();
+ rng.nextBytes(mIv);
+// // IV was not provided by the caller and thus will be generated by keymaster.
+// // Mix in some additional entropy from the provided SecureRandom.
+// if (mRng != null) {
+// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes];
+// mRng.nextBytes(mAdditionalEntropyForBegin);
+// }
+ }
+ }
+ }
+
+ if ((mIvRequired) && (mIv != null)) {
+ keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
+ }
+ }
+
+ /**
+ * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the
+ * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after
+ * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}.
+ *
+ * <p>The default implementation only takes care of the IV.
+ *
+ * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
+ * operation.
+ */
+ protected void loadAlgorithmSpecificParametersFromBeginResult(
+ KeymasterArguments keymasterArgs) {
+ // NOTE: Keymaster doesn't always return an IV, even if it's used.
+ byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null);
+ if ((returnedIv != null) && (returnedIv.length == 0)) {
+ returnedIv = null;
+ }
+
+ if (mIvRequired) {
+ if (mIv == null) {
+ mIv = returnedIv;
+ } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
+ throw new CryptoOperationException("IV in use differs from provided IV");
+ }
+ } else {
+ if (returnedIv != null) {
+ throw new CryptoOperationException(
+ "IV in use despite IV not being used by this transformation");
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java
new file mode 100644
index 0000000..8ed6e04
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreConnectException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * Indicates a communications error with keystore service.
+ *
+ * @hide
+ */
+public class KeyStoreConnectException extends CryptoOperationException {
+ public KeyStoreConnectException() {
+ super("Failed to communicate with keystore service");
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/KeyStoreCryptoOperation.java
new file mode 100644
index 0000000..19abd05
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCryptoOperation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * Cryptographic operation backed by {@link KeyStore}.
+ *
+ * @hide
+ */
+public interface KeyStoreCryptoOperation {
+ /**
+ * Gets the KeyStore operation handle of this crypto operation.
+ *
+ * @return handle or {@code null} if the KeyStore operation is not in progress.
+ */
+ Long getOperationHandle();
+}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
new file mode 100644
index 0000000..1f8b7e4
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.os.IBinder;
+import android.security.keymaster.OperationResult;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
+ * {@code update} and {@code finish} operations.
+ *
+ * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
+ * update and finish operations. Firstly, KeyStore's update operation can consume only a limited
+ * amount of data in one go because the operations are marshalled via Binder. Secondly, the update
+ * operation may consume less data than provided, in which case the caller has to buffer the
+ * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
+ * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement
+ * various JCA crypto primitives.
+ *
+ * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
+ * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
+ * parameters to {@code update} and {@code final} operations.
+ *
+ * @hide
+ */
+public class KeyStoreCryptoOperationChunkedStreamer {
+
+ /**
+ * Bidirectional chunked data stream over a KeyStore crypto operation.
+ */
+ public interface Stream {
+ /**
+ * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't
+ * be reached.
+ */
+ OperationResult update(byte[] input);
+
+ /**
+ * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't
+ * be reached.
+ */
+ OperationResult finish();
+ }
+
+ // Binder buffer is about 1MB, but it's shared between all active transactions of the process.
+ // Thus, it's safer to use a much smaller upper bound.
+ private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ private final Stream mKeyStoreStream;
+ private final int mMaxChunkSize;
+
+ private byte[] mBuffered = EMPTY_BYTE_ARRAY;
+ private int mBufferedOffset;
+ private int mBufferedLength;
+
+ public KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
+ this(operation, DEFAULT_MAX_CHUNK_SIZE);
+ }
+
+ public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) {
+ mKeyStoreStream = operation;
+ mMaxChunkSize = maxChunkSize;
+ }
+
+ public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
+ if (inputLength == 0) {
+ // No input provided
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ ByteArrayOutputStream bufferedOutput = null;
+
+ while (inputLength > 0) {
+ byte[] chunk;
+ int inputBytesInChunk;
+ if ((mBufferedLength + inputLength) > mMaxChunkSize) {
+ // Too much input for one chunk -- extract one max-sized chunk and feed it into the
+ // update operation.
+ inputBytesInChunk = mMaxChunkSize - mBufferedLength;
+ chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
+ input, inputOffset, inputBytesInChunk);
+ } else {
+ // All of available input fits into one chunk.
+ if ((mBufferedLength == 0) && (inputOffset == 0)
+ && (inputLength == input.length)) {
+ // Nothing buffered and all of input array needs to be fed into the update
+ // operation.
+ chunk = input;
+ inputBytesInChunk = input.length;
+ } else {
+ // Need to combine buffered data with input data into one array.
+ inputBytesInChunk = inputLength;
+ chunk = concat(mBuffered, mBufferedOffset, mBufferedLength,
+ input, inputOffset, inputBytesInChunk);
+ }
+ }
+ // Update input array references to reflect that some of its bytes are now in mBuffered.
+ inputOffset += inputBytesInChunk;
+ inputLength -= inputBytesInChunk;
+
+ OperationResult opResult = mKeyStoreStream.update(chunk);
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getKeyStoreException(opResult.resultCode);
+ }
+
+ if (opResult.inputConsumed == chunk.length) {
+ // The whole chunk was consumed
+ mBuffered = EMPTY_BYTE_ARRAY;
+ mBufferedOffset = 0;
+ mBufferedLength = 0;
+ } else if (opResult.inputConsumed == 0) {
+ // Nothing was consumed. More input needed.
+ if (inputLength > 0) {
+ // More input is available, but it wasn't included into the previous chunk
+ // because the chunk reached its maximum permitted size.
+ // Shouldn't have happened.
+ throw new CryptoOperationException("Nothing consumed from max-sized chunk: "
+ + chunk.length + " bytes");
+ }
+ mBuffered = chunk;
+ mBufferedOffset = 0;
+ mBufferedLength = chunk.length;
+ } else if (opResult.inputConsumed < chunk.length) {
+ // The chunk was consumed only partially -- buffer the rest of the chunk
+ mBuffered = chunk;
+ mBufferedOffset = opResult.inputConsumed;
+ mBufferedLength = chunk.length - opResult.inputConsumed;
+ } else {
+ throw new CryptoOperationException("Consumed more than provided: "
+ + opResult.inputConsumed + ", provided: " + chunk.length);
+ }
+
+ if ((opResult.output != null) && (opResult.output.length > 0)) {
+ if (inputLength > 0) {
+ // More output might be produced in this loop -- buffer the current output
+ if (bufferedOutput == null) {
+ bufferedOutput = new ByteArrayOutputStream();
+ try {
+ bufferedOutput.write(opResult.output);
+ } catch (IOException e) {
+ throw new CryptoOperationException("Failed to buffer output", e);
+ }
+ }
+ } else {
+ // No more output will be produced in this loop
+ if (bufferedOutput == null) {
+ // No previously buffered output
+ return opResult.output;
+ } else {
+ // There was some previously buffered output
+ try {
+ bufferedOutput.write(opResult.output);
+ } catch (IOException e) {
+ throw new CryptoOperationException("Failed to buffer output", e);
+ }
+ return bufferedOutput.toByteArray();
+ }
+ }
+ }
+ }
+
+ if (bufferedOutput == null) {
+ // No output produced
+ return EMPTY_BYTE_ARRAY;
+ } else {
+ return bufferedOutput.toByteArray();
+ }
+ }
+
+ public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
+ throws KeyStoreException {
+ if (inputLength == 0) {
+ // No input provided -- simplify the rest of the code
+ input = EMPTY_BYTE_ARRAY;
+ inputOffset = 0;
+ }
+
+ // Flush all buffered input and provided input into keystore/keymaster.
+ byte[] output = update(input, inputOffset, inputLength);
+ output = concat(output, flush());
+
+ OperationResult opResult = mKeyStoreStream.finish();
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getKeyStoreException(opResult.resultCode);
+ }
+
+ return concat(output, opResult.output);
+ }
+
+ /**
+ * Passes all of buffered input into the the KeyStore operation (via the {@code update}
+ * operation) and returns output.
+ */
+ public byte[] flush() throws KeyStoreException {
+ if (mBufferedLength <= 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength);
+ mBuffered = EMPTY_BYTE_ARRAY;
+ mBufferedLength = 0;
+ mBufferedOffset = 0;
+
+ OperationResult opResult = mKeyStoreStream.update(chunk);
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getKeyStoreException(opResult.resultCode);
+ }
+
+ if (opResult.inputConsumed < chunk.length) {
+ throw new CryptoOperationException("Keystore failed to consume all input. Provided: "
+ + chunk.length + ", consumed: " + opResult.inputConsumed);
+ } else if (opResult.inputConsumed > chunk.length) {
+ throw new CryptoOperationException("Keystore consumed more input than provided"
+ + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed);
+ }
+
+ return (opResult.output != null) ? opResult.output : EMPTY_BYTE_ARRAY;
+ }
+
+ private static byte[] concat(byte[] arr1, byte[] arr2) {
+ if ((arr1 == null) || (arr1.length == 0)) {
+ return arr2;
+ } else if ((arr2 == null) || (arr2.length == 0)) {
+ return arr1;
+ } else {
+ byte[] result = new byte[arr1.length + arr2.length];
+ System.arraycopy(arr1, 0, result, 0, arr1.length);
+ System.arraycopy(arr2, 0, result, arr1.length, arr2.length);
+ return result;
+ }
+ }
+
+ private static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2,
+ int len2) {
+ if (len1 == 0) {
+ return subarray(arr2, offset2, len2);
+ } else if (len2 == 0) {
+ return subarray(arr1, offset1, len1);
+ } else {
+ byte[] result = new byte[len1 + len2];
+ System.arraycopy(arr1, offset1, result, 0, len1);
+ System.arraycopy(arr2, offset2, result, len1, len2);
+ return result;
+ }
+ }
+
+ private static byte[] subarray(byte[] arr, int offset, int len) {
+ if (len == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ if ((offset == 0) && (len == arr.length)) {
+ return arr;
+ }
+ byte[] result = new byte[len];
+ System.arraycopy(arr, offset, result, 0, len);
+ return result;
+ }
+
+ /**
+ * Main data stream via a KeyStore streaming operation.
+ *
+ * <p>For example, for an encryption operation, this is the stream through which plaintext is
+ * provided and ciphertext is obtained.
+ */
+ public static class MainDataStream implements Stream {
+
+ private final KeyStore mKeyStore;
+ private final IBinder mOperationToken;
+
+ public MainDataStream(KeyStore keyStore, IBinder operationToken) {
+ mKeyStore = keyStore;
+ mOperationToken = operationToken;
+ }
+
+ @Override
+ public OperationResult update(byte[] input) {
+ return mKeyStore.update(mOperationToken, null, input);
+ }
+
+ @Override
+ public OperationResult finish() {
+ return mKeyStore.finish(mOperationToken, null, null);
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
new file mode 100644
index 0000000..88e768c
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * KeyStore/keymaster exception with positive error codes coming from the KeyStore and negative
+ * ones from keymaster.
+ *
+ * @hide
+ */
+public class KeyStoreException extends Exception {
+
+ private final int mErrorCode;
+
+ public KeyStoreException(int errorCode, String message) {
+ super(message);
+ mErrorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
new file mode 100644
index 0000000..f69e7d1
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.os.IBinder;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.MacSpi;
+
+/**
+ * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
+
+ public static class HmacSHA1 extends KeyStoreHmacSpi {
+ public HmacSHA1() {
+ super(KeyStoreKeyConstraints.Digest.SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends KeyStoreHmacSpi {
+ public HmacSHA224() {
+ super(KeyStoreKeyConstraints.Digest.SHA224);
+ }
+ }
+
+ public static class HmacSHA256 extends KeyStoreHmacSpi {
+ public HmacSHA256() {
+ super(KeyStoreKeyConstraints.Digest.SHA256);
+ }
+ }
+
+ public static class HmacSHA384 extends KeyStoreHmacSpi {
+ public HmacSHA384() {
+ super(KeyStoreKeyConstraints.Digest.SHA384);
+ }
+ }
+
+ public static class HmacSHA512 extends KeyStoreHmacSpi {
+ public HmacSHA512() {
+ super(KeyStoreKeyConstraints.Digest.SHA512);
+ }
+ }
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private final @KeyStoreKeyConstraints.DigestEnum int mDigest;
+ private final int mMacSizeBytes;
+
+ private String mKeyAliasInKeyStore;
+
+ // The fields below are reset by the engineReset operation.
+ private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
+ private IBinder mOperationToken;
+ private Long mOperationHandle;
+
+ protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest) {
+ mDigest = digest;
+ mMacSizeBytes = KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
+ }
+
+ @Override
+ protected int engineGetMacLength() {
+ return mMacSizeBytes;
+ }
+
+ @Override
+ protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
+ InvalidAlgorithmParameterException {
+ if (key == null) {
+ throw new InvalidKeyException("key == null");
+ } else if (!(key instanceof KeyStoreSecretKey)) {
+ throw new InvalidKeyException(
+ "Only Android KeyStore secret keys supported. Key: " + key);
+ }
+
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported algorithm parameters: " + params);
+ }
+
+ mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias();
+ if (mKeyAliasInKeyStore == null) {
+ throw new InvalidKeyException("Key's KeyStore alias not known");
+ }
+ engineReset();
+ ensureKeystoreOperationInitialized();
+ }
+
+ @Override
+ protected void engineReset() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mOperationHandle = null;
+ mChunkedStreamer = null;
+ }
+
+ private void ensureKeystoreOperationInitialized() {
+ if (mChunkedStreamer != null) {
+ return;
+ }
+ if (mKeyAliasInKeyStore == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ KeymasterArguments keymasterArgs = new KeymasterArguments();
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeyStoreKeyConstraints.Algorithm.HMAC);
+ keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mDigest);
+
+ OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore,
+ KeymasterDefs.KM_PURPOSE_SIGN,
+ true,
+ keymasterArgs,
+ null,
+ new KeymasterArguments());
+ if (opResult == null) {
+ throw new KeyStoreConnectException();
+ } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getCryptoOperationException(opResult.resultCode);
+ }
+ if (opResult.token == null) {
+ throw new CryptoOperationException("Keystore returned null operation token");
+ }
+ mOperationToken = opResult.token;
+ mOperationHandle = opResult.operationHandle;
+ mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+ new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+ mKeyStore, mOperationToken));
+ }
+
+ @Override
+ protected void engineUpdate(byte input) {
+ engineUpdate(new byte[] {input}, 0, 1);
+ }
+
+ @Override
+ protected void engineUpdate(byte[] input, int offset, int len) {
+ ensureKeystoreOperationInitialized();
+
+ byte[] output;
+ try {
+ output = mChunkedStreamer.update(input, offset, len);
+ } catch (KeyStoreException e) {
+ throw KeyStore.getCryptoOperationException(e);
+ }
+ if ((output != null) && (output.length != 0)) {
+ throw new CryptoOperationException("Update operation unexpectedly produced output");
+ }
+ }
+
+ @Override
+ protected byte[] engineDoFinal() {
+ ensureKeystoreOperationInitialized();
+
+ byte[] result;
+ try {
+ result = mChunkedStreamer.doFinal(null, 0, 0);
+ } catch (KeyStoreException e) {
+ throw KeyStore.getCryptoOperationException(e);
+ }
+
+ engineReset();
+ return result;
+ }
+
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mKeyStore.abort(operationToken);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public Long getOperationHandle() {
+ return mOperationHandle;
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
new file mode 100644
index 0000000..543b5d8
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.annotation.IntDef;
+import android.security.keymaster.KeymasterDefs;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Characteristics of {@code AndroidKeyStore} keys.
+ *
+ * @hide
+ */
+public abstract class KeyStoreKeyCharacteristics {
+ private KeyStoreKeyCharacteristics() {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED})
+ public @interface OriginEnum {}
+
+ /**
+ * Origin of the key.
+ */
+ public static abstract class Origin {
+ private Origin() {}
+
+ /** Key was generated inside a TEE. */
+ public static final int GENERATED_INSIDE_TEE = 1;
+
+ /** Key was generated outside of a TEE. */
+ public static final int GENERATED_OUTSIDE_OF_TEE = 2;
+
+ /** Key was imported. */
+ public static final int IMPORTED = 0;
+
+ /**
+ * @hide
+ */
+ public static @OriginEnum int fromKeymaster(int origin) {
+ switch (origin) {
+ case KeymasterDefs.KM_ORIGIN_HARDWARE:
+ return GENERATED_INSIDE_TEE;
+ case KeymasterDefs.KM_ORIGIN_SOFTWARE:
+ return GENERATED_OUTSIDE_OF_TEE;
+ case KeymasterDefs.KM_ORIGIN_IMPORTED:
+ return IMPORTED;
+ default:
+ throw new IllegalArgumentException("Unknown origin: " + origin);
+ }
+ }
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 01e6dcd..9321302 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.security;
import android.annotation.IntDef;
@@ -5,7 +21,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
@@ -18,7 +33,8 @@
private KeyStoreKeyConstraints() {}
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag=true, value={Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
+ @IntDef(flag = true,
+ value = {Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
public @interface PurposeEnum {}
/**
@@ -48,11 +64,6 @@
public static final int VERIFY = 1 << 3;
/**
- * Number of flags defined above. Needs to be kept in sync with the flags above.
- */
- private static final int VALUE_COUNT = 4;
-
- /**
* @hide
*/
public static int toKeymaster(@PurposeEnum int purpose) {
@@ -91,22 +102,12 @@
/**
* @hide
*/
- public static int[] allToKeymaster(int purposes) {
- int[] result = new int[VALUE_COUNT];
- int resultCount = 0;
- int purpose = 1;
- for (int i = 0; i < 32; i++) {
- if ((purposes & 1) != 0) {
- result[resultCount] = toKeymaster(purpose);
- resultCount++;
- }
- purposes >>>= 1;
- purpose <<= 1;
- if (purposes == 0) {
- break;
- }
+ public static int[] allToKeymaster(@PurposeEnum int purposes) {
+ int[] result = getSetFlags(purposes);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
}
- return Arrays.copyOf(result, resultCount);
+ return result;
}
/**
@@ -222,20 +223,11 @@
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
}
}
-
- /**
- * @hide
- */
- public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
- switch (algorithm) {
- default:
- throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
- }
- }
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Padding.NONE, Padding.ZERO, Padding.PKCS7})
+ @IntDef(flag = true,
+ value = {Padding.NONE, Padding.PKCS7})
public @interface PaddingEnum {}
/**
@@ -247,17 +239,12 @@
/**
* No padding.
*/
- public static final int NONE = 0;
-
- /**
- * Pad with zeros.
- */
- public static final int ZERO = 1;
+ public static final int NONE = 1 << 0;
/**
* PKCS#7 padding.
*/
- public static final int PKCS7 = 2;
+ public static final int PKCS7 = 1 << 1;
/**
* @hide
@@ -266,8 +253,6 @@
switch (padding) {
case NONE:
return KeymasterDefs.KM_PAD_NONE;
- case ZERO:
- return KeymasterDefs.KM_PAD_ZERO;
case PKCS7:
return KeymasterDefs.KM_PAD_PKCS7;
default:
@@ -282,18 +267,75 @@
switch (padding) {
case KeymasterDefs.KM_PAD_NONE:
return NONE;
- case KeymasterDefs.KM_PAD_ZERO:
- return ZERO;
case KeymasterDefs.KM_PAD_PKCS7:
return PKCS7;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
}
+
+ /**
+ * @hide
+ */
+ public static String toString(@PaddingEnum int padding) {
+ switch (padding) {
+ case NONE:
+ return "NONE";
+ case PKCS7:
+ return "PKCS#7";
+ default:
+ throw new IllegalArgumentException("Unknown padding: " + padding);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @PaddingEnum int fromJCAPadding(String padding) {
+ String paddingLower = padding.toLowerCase(Locale.US);
+ if ("nopadding".equals(paddingLower)) {
+ return NONE;
+ } else if ("pkcs7padding".equals(paddingLower)) {
+ return PKCS7;
+ } else {
+ throw new IllegalArgumentException("Unknown padding: " + padding);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static int[] allToKeymaster(@PaddingEnum int paddings) {
+ int[] result = getSetFlags(paddings);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @PaddingEnum int allFromKeymaster(Collection<Integer> paddings) {
+ @PaddingEnum int result = 0;
+ for (int keymasterPadding : paddings) {
+ result |= fromKeymaster(keymasterPadding);
+ }
+ return result;
+ }
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Digest.NONE, Digest.SHA256})
+ @IntDef(flag = true,
+ value = {
+ Digest.NONE,
+ Digest.MD5,
+ Digest.SHA1,
+ Digest.SHA224,
+ Digest.SHA256,
+ Digest.SHA384,
+ Digest.SHA512,
+ })
public @interface DigestEnum {}
/**
@@ -306,12 +348,37 @@
/**
* No digest: sign/authenticate the raw message.
*/
- public static final int NONE = 0;
+ public static final int NONE = 1 << 0;
/**
- * SHA-256 digest.
+ * MD5 digest.
*/
- public static final int SHA256 = 1;
+ public static final int MD5 = 1 << 1;
+
+ /**
+ * SHA-1 digest.
+ */
+ public static final int SHA1 = 1 << 2;
+
+ /**
+ * SHA-2 224 (aka SHA-224) digest.
+ */
+ public static final int SHA224 = 1 << 3;
+
+ /**
+ * SHA-2 256 (aka SHA-256) digest.
+ */
+ public static final int SHA256 = 1 << 4;
+
+ /**
+ * SHA-2 384 (aka SHA-384) digest.
+ */
+ public static final int SHA384 = 1 << 5;
+
+ /**
+ * SHA-2 512 (aka SHA-512) digest.
+ */
+ public static final int SHA512 = 1 << 6;
/**
* @hide
@@ -320,8 +387,18 @@
switch (digest) {
case NONE:
return "NONE";
+ case MD5:
+ return "MD5";
+ case SHA1:
+ return "SHA-1";
+ case SHA224:
+ return "SHA-224";
case SHA256:
- return "SHA256";
+ return "SHA-256";
+ case SHA384:
+ return "SHA-384";
+ case SHA512:
+ return "SHA-512";
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -330,12 +407,40 @@
/**
* @hide
*/
+ public static String allToString(@DigestEnum int digests) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@DigestEnum int digest : getSetFlags(digests)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(digest));
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ /**
+ * @hide
+ */
public static int toKeymaster(@DigestEnum int digest) {
switch (digest) {
case NONE:
return KeymasterDefs.KM_DIGEST_NONE;
+ case MD5:
+ return KeymasterDefs.KM_DIGEST_MD5;
+ case SHA1:
+ return KeymasterDefs.KM_DIGEST_SHA1;
+ case SHA224:
+ return KeymasterDefs.KM_DIGEST_SHA_2_224;
case SHA256:
return KeymasterDefs.KM_DIGEST_SHA_2_256;
+ case SHA384:
+ return KeymasterDefs.KM_DIGEST_SHA_2_384;
+ case SHA512:
+ return KeymasterDefs.KM_DIGEST_SHA_2_512;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -348,8 +453,18 @@
switch (digest) {
case KeymasterDefs.KM_DIGEST_NONE:
return NONE;
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return MD5;
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return SHA1;
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return SHA224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return SHA256;
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return SHA384;
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return SHA512;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -358,14 +473,46 @@
/**
* @hide
*/
+ public static int[] allToKeymaster(@DigestEnum int digests) {
+ int[] result = getSetFlags(digests);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @DigestEnum int allFromKeymaster(Collection<Integer> digests) {
+ @DigestEnum int result = 0;
+ for (int keymasterDigest : digests) {
+ result |= fromKeymaster(keymasterDigest);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) {
String algorithmLower = algorithm.toLowerCase(Locale.US);
if (algorithmLower.startsWith("hmac")) {
- if ("hmacsha256".equals(algorithmLower)) {
+ String digestLower = algorithmLower.substring("hmac".length());
+ if ("md5".equals(digestLower)) {
+ return MD5;
+ } else if ("sha1".equals(digestLower)) {
+ return SHA1;
+ } else if ("sha224".equals(digestLower)) {
+ return SHA224;
+ } else if ("sha256".equals(digestLower)) {
return SHA256;
+ } else if ("sha384".equals(digestLower)) {
+ return SHA384;
+ } else if ("sha512".equals(digestLower)) {
+ return SHA512;
} else {
- throw new IllegalArgumentException("Unsupported digest: "
- + algorithmLower.substring("hmac".length()));
+ throw new IllegalArgumentException("Unsupported digest: " + digestLower);
}
} else {
return null;
@@ -379,8 +526,42 @@
switch (digest) {
case NONE:
return "NONE";
+ case MD5:
+ return "MD5";
+ case SHA1:
+ return "SHA1";
+ case SHA224:
+ return "SHA224";
case SHA256:
return "SHA256";
+ case SHA384:
+ return "SHA384";
+ case SHA512:
+ return "SHA512";
+ default:
+ throw new IllegalArgumentException("Unknown digest: " + digest);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static Integer getOutputSizeBytes(@DigestEnum int digest) {
+ switch (digest) {
+ case NONE:
+ return null;
+ case MD5:
+ return 128 / 8;
+ case SHA1:
+ return 160 / 8;
+ case SHA224:
+ return 224 / 8;
+ case SHA256:
+ return 256 / 8;
+ case SHA384:
+ return 384 / 8;
+ case SHA512:
+ return 512 / 8;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -388,7 +569,8 @@
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({BlockMode.ECB})
+ @IntDef(flag = true,
+ value = {BlockMode.ECB, BlockMode.CBC, BlockMode.CTR, BlockMode.GCM})
public @interface BlockModeEnum {}
/**
@@ -397,10 +579,17 @@
public static abstract class BlockMode {
private BlockMode() {}
- /**
- * Electronic Codebook (ECB) block mode.
- */
- public static final int ECB = 0;
+ /** Electronic Codebook (ECB) block mode. */
+ public static final int ECB = 1 << 0;
+
+ /** Cipher Block Chaining (CBC) block mode. */
+ public static final int CBC = 1 << 1;
+
+ /** Counter (CTR) block mode. */
+ public static final int CTR = 1 << 2;
+
+ /** Galois/Counter Mode (GCM) block mode. */
+ public static final int GCM = 1 << 3;
/**
* @hide
@@ -409,6 +598,12 @@
switch (mode) {
case ECB:
return KeymasterDefs.KM_MODE_ECB;
+ case CBC:
+ return KeymasterDefs.KM_MODE_CBC;
+ case CTR:
+ return KeymasterDefs.KM_MODE_CTR;
+ case GCM:
+ return KeymasterDefs.KM_MODE_GCM;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -421,9 +616,193 @@
switch (mode) {
case KeymasterDefs.KM_MODE_ECB:
return ECB;
+ case KeymasterDefs.KM_MODE_CBC:
+ return CBC;
+ case KeymasterDefs.KM_MODE_CTR:
+ return CTR;
+ case KeymasterDefs.KM_MODE_GCM:
+ return GCM;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
}
+
+ /**
+ * @hide
+ */
+ public static int[] allToKeymaster(@BlockModeEnum int modes) {
+ int[] result = getSetFlags(modes);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toKeymaster(result[i]);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @BlockModeEnum int allFromKeymaster(Collection<Integer> modes) {
+ @BlockModeEnum int result = 0;
+ for (int keymasterMode : modes) {
+ result |= fromKeymaster(keymasterMode);
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static String toString(@BlockModeEnum int mode) {
+ switch (mode) {
+ case ECB:
+ return "ECB";
+ case CBC:
+ return "CBC";
+ case CTR:
+ return "CTR";
+ case GCM:
+ return "GCM";
+ default:
+ throw new IllegalArgumentException("Unknown block mode: " + mode);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @BlockModeEnum int fromJCAMode(String mode) {
+ String modeLower = mode.toLowerCase(Locale.US);
+ if ("ecb".equals(modeLower)) {
+ return ECB;
+ } else if ("cbc".equals(modeLower)) {
+ return CBC;
+ } else if ("ctr".equals(modeLower)) {
+ return CTR;
+ } else if ("gcm".equals(modeLower)) {
+ return GCM;
+ } else {
+ throw new IllegalArgumentException("Unknown block mode: " + mode);
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {UserAuthenticator.LOCK_SCREEN})
+ public @interface UserAuthenticatorEnum {}
+
+ /**
+ * User authenticators which can be used to restrict/protect access to keys.
+ */
+ public static abstract class UserAuthenticator {
+ private UserAuthenticator() {}
+
+ /** Lock screen. */
+ public static final int LOCK_SCREEN = 1 << 0;
+
+ /**
+ * @hide
+ */
+ public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) {
+ switch (userAuthenticator) {
+ case LOCK_SCREEN:
+ return KeymasterDefs.HW_AUTH_PASSWORD;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown user authenticator: " + userAuthenticator);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) {
+ switch (userAuthenticator) {
+ case KeymasterDefs.HW_AUTH_PASSWORD:
+ return LOCK_SCREEN;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown user authenticator: " + userAuthenticator);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static int allToKeymaster(@UserAuthenticatorEnum int userAuthenticators) {
+ int result = 0;
+ int userAuthenticator = 1;
+ while (userAuthenticators != 0) {
+ if ((userAuthenticators & 1) != 0) {
+ result |= toKeymaster(userAuthenticator);
+ }
+ userAuthenticators >>>= 1;
+ userAuthenticator <<= 1;
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static @UserAuthenticatorEnum int allFromKeymaster(int userAuthenticators) {
+ @UserAuthenticatorEnum int result = 0;
+ int userAuthenticator = 1;
+ while (userAuthenticators != 0) {
+ if ((userAuthenticators & 1) != 0) {
+ result |= fromKeymaster(userAuthenticator);
+ }
+ userAuthenticators >>>= 1;
+ userAuthenticator <<= 1;
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public static String toString(@UserAuthenticatorEnum int userAuthenticator) {
+ switch (userAuthenticator) {
+ case LOCK_SCREEN:
+ return "LOCK_SCREEN";
+ default:
+ throw new IllegalArgumentException(
+ "Unknown user authenticator: " + userAuthenticator);
+ }
+ }
+ }
+
+ private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+ private static int[] getSetFlags(int flags) {
+ if (flags == 0) {
+ return EMPTY_INT_ARRAY;
+ }
+ int result[] = new int[getSetBitCount(flags)];
+ int resultOffset = 0;
+ int flag = 1;
+ while (flags != 0) {
+ if ((flags & 1) != 0) {
+ result[resultOffset] = flag;
+ resultOffset++;
+ }
+ flags >>>= 1;
+ flag <<= 1;
+ }
+ return result;
+ }
+
+ private static int getSetBitCount(int value) {
+ if (value == 0) {
+ return 0;
+ }
+ int result = 0;
+ while (value != 0) {
+ if ((value & 1) != 0) {
+ result++;
+ }
+ value >>>= 1;
+ }
+ return result;
}
}
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
new file mode 100644
index 0000000..5be8c39
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Date;
+
+import javax.crypto.KeyGeneratorSpi;
+import javax.crypto.SecretKey;
+
+/**
+ * {@link KeyGeneratorSpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
+
+ public static class AES extends KeyStoreKeyGeneratorSpi {
+ public AES() {
+ super(KeyStoreKeyConstraints.Algorithm.AES, 128);
+ }
+ }
+
+ protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi {
+ protected HmacBase(@KeyStoreKeyConstraints.DigestEnum int digest) {
+ super(KeyStoreKeyConstraints.Algorithm.HMAC,
+ digest,
+ KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest) * 8);
+ }
+ }
+
+ public static class HmacSHA1 extends HmacBase {
+ public HmacSHA1() {
+ super(KeyStoreKeyConstraints.Digest.SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends HmacBase {
+ public HmacSHA224() {
+ super(KeyStoreKeyConstraints.Digest.SHA224);
+ }
+ }
+
+ public static class HmacSHA256 extends HmacBase {
+ public HmacSHA256() {
+ super(KeyStoreKeyConstraints.Digest.SHA256);
+ }
+ }
+
+ public static class HmacSHA384 extends HmacBase {
+ public HmacSHA384() {
+ super(KeyStoreKeyConstraints.Digest.SHA384);
+ }
+ }
+
+ public static class HmacSHA512 extends HmacBase {
+ public HmacSHA512() {
+ super(KeyStoreKeyConstraints.Digest.SHA512);
+ }
+ }
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
+ private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
+ private final int mDefaultKeySizeBits;
+
+ private KeyGeneratorSpec mSpec;
+ private SecureRandom mRng;
+
+ protected KeyStoreKeyGeneratorSpi(
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
+ int defaultKeySizeBits) {
+ this(algorithm, null, defaultKeySizeBits);
+ }
+
+ protected KeyStoreKeyGeneratorSpi(
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
+ @KeyStoreKeyConstraints.DigestEnum Integer digest,
+ int defaultKeySizeBits) {
+ mAlgorithm = algorithm;
+ mDigest = digest;
+ mDefaultKeySizeBits = defaultKeySizeBits;
+ }
+
+ @Override
+ protected SecretKey engineGenerateKey() {
+ KeyGeneratorSpec spec = mSpec;
+ if (spec == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+
+ if ((spec.isEncryptionRequired())
+ && (mKeyStore.state() != KeyStore.State.UNLOCKED)) {
+ throw new IllegalStateException(
+ "Android KeyStore must be in initialized and unlocked state if encryption is"
+ + " required");
+ }
+
+ KeymasterArguments args = new KeymasterArguments();
+ args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
+ KeyStoreKeyConstraints.Algorithm.toKeymaster(mAlgorithm));
+ if (mDigest != null) {
+ args.addInt(KeymasterDefs.KM_TAG_DIGEST,
+ KeyStoreKeyConstraints.Digest.toKeymaster(mDigest));
+ Integer digestOutputSizeBytes =
+ KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
+ if (digestOutputSizeBytes != null) {
+ // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+ // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
+ args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
+ }
+ }
+ if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+ if (mDigest == null) {
+ throw new IllegalStateException("Digest algorithm must be specified for key"
+ + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
+ }
+ }
+ int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
+ args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
+ int purposes = spec.getPurposes();
+ for (int keymasterPurpose :
+ KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
+ args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
+ }
+ for (int keymasterBlockMode :
+ KeyStoreKeyConstraints.BlockMode.allToKeymaster(spec.getBlockModes())) {
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
+ }
+ for (int keymasterPadding :
+ KeyStoreKeyConstraints.Padding.allToKeymaster(spec.getPaddings())) {
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
+ }
+ if (spec.getUserAuthenticators() == 0) {
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
+ } else {
+ args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
+ KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster(
+ spec.getUserAuthenticators()));
+ }
+ if (spec.getUserAuthenticationValidityDurationSeconds() != -1) {
+ args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
+ spec.getUserAuthenticationValidityDurationSeconds());
+ }
+ args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ (spec.getKeyValidityStart() != null)
+ ? spec.getKeyValidityStart() : new Date(0));
+ args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ (spec.getKeyValidityForOriginationEnd() != null)
+ ? spec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ (spec.getKeyValidityForConsumptionEnd() != null)
+ ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
+
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
+ // Permit caller-specified IV. This is needed due to the Cipher abstraction.
+ args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
+ }
+
+ byte[] additionalEntropy = null;
+ SecureRandom rng = mRng;
+ if (rng != null) {
+ additionalEntropy = new byte[(keySizeBits + 7) / 8];
+ rng.nextBytes(additionalEntropy);
+ }
+
+ int flags = spec.getFlags();
+ String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias();
+ int errorCode = mKeyStore.generateKey(
+ keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics());
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw KeyStore.getCryptoOperationException(errorCode);
+ }
+ String keyAlgorithmJCA =
+ KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(mAlgorithm, mDigest);
+ return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA);
+ }
+
+ @Override
+ protected void engineInit(SecureRandom random) {
+ throw new UnsupportedOperationException("Cannot initialize without an "
+ + KeyGeneratorSpec.class.getName() + " parameter");
+ }
+
+ @Override
+ protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ if ((params == null) || (!(params instanceof KeyGeneratorSpec))) {
+ throw new InvalidAlgorithmParameterException("Cannot initialize without an "
+ + KeyGeneratorSpec.class.getName() + " parameter");
+ }
+ KeyGeneratorSpec spec = (KeyGeneratorSpec) params;
+ if (spec.getKeystoreAlias() == null) {
+ throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
+ }
+
+ mSpec = spec;
+ mRng = random;
+ }
+
+ @Override
+ protected void engineInit(int keySize, SecureRandom random) {
+ throw new UnsupportedOperationException("Cannot initialize without a "
+ + KeyGeneratorSpec.class.getName() + " parameter");
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
new file mode 100644
index 0000000..df4c958
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreKeySpec.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import java.security.spec.KeySpec;
+import java.util.Date;
+
+/**
+ * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android
+ * KeyStore</a>.
+ *
+ * @hide
+ */
+public class KeyStoreKeySpec implements KeySpec {
+ private final String mKeystoreAlias;
+ private final int mKeySize;
+ private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin;
+ private final Date mKeyValidityStart;
+ private final Date mKeyValidityForOriginationEnd;
+ private final Date mKeyValidityForConsumptionEnd;
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private final @KeyStoreKeyConstraints.DigestEnum int mDigests;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mTeeEnforcedUserAuthenticators;
+ private final int mUserAuthenticationValidityDurationSeconds;
+
+
+ /**
+ * @hide
+ */
+ KeyStoreKeySpec(String keystoreKeyAlias,
+ @KeyStoreKeyCharacteristics.OriginEnum int origin,
+ int keySize,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.DigestEnum int digests,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators,
+ int userAuthenticationValidityDurationSeconds) {
+ mKeystoreAlias = keystoreKeyAlias;
+ mOrigin = origin;
+ mKeySize = keySize;
+ mKeyValidityStart = keyValidityStart;
+ mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
+ mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
+ mPurposes = purposes;
+ mAlgorithm = algorithm;
+ mPaddings = paddings;
+ mDigests = digests;
+ mBlockModes = blockModes;
+ mUserAuthenticators = userAuthenticators;
+ mTeeEnforcedUserAuthenticators = teeEnforcedUserAuthenticators;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ }
+
+ /**
+ * Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}.
+ */
+ public String getKeystoreAlias() {
+ return mKeystoreAlias;
+ }
+
+ /**
+ * Gets the origin of the key.
+ */
+ public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() {
+ return mOrigin;
+ }
+
+ /**
+ * Gets the size of the key in bits.
+ */
+ public int getKeySize() {
+ return mKeySize;
+ }
+
+ /**
+ * Gets the time instant before which the key is not yet valid.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityStart() {
+ return mKeyValidityStart;
+ }
+
+ /**
+ * Gets the time instant after which the key is no long valid for decryption and verification.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityForConsumptionEnd() {
+ return mKeyValidityForConsumptionEnd;
+ }
+
+ /**
+ * Gets the time instant after which the key is no long valid for encryption and signing.
+ *
+ * @return instant or {@code null} if not restricted.
+ */
+ public Date getKeyValidityForOriginationEnd() {
+ return mKeyValidityForOriginationEnd;
+ }
+
+ /**
+ * Gets the set of purposes for which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
+ return mPurposes;
+ }
+
+ /**
+ * Gets the algorithm of the key.
+ */
+ public @KeyStoreKeyConstraints.AlgorithmEnum int getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ /**
+ * Gets the set of block modes with which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
+ }
+
+ /**
+ * Gets the set of padding modes with which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
+ }
+
+ /**
+ * Gets the set of digest algorithms with which the key can be used.
+ */
+ public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+ return mDigests;
+ }
+
+ /**
+ * Gets the set of user authenticators which protect access to the key. The key can only be used
+ * iff the user has authenticated to at least one of these user authenticators.
+ *
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
+ }
+
+ /**
+ * Gets the set of user authenticators for which the TEE enforces access restrictions for this
+ * key. This is a subset of the user authentications returned by
+ * {@link #getUserAuthenticators()}.
+ */
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getTeeEnforcedUserAuthenticators() {
+ return mTeeEnforcedUserAuthenticators;
+ }
+
+ /**
+ * Gets the duration of time (seconds) for which the key can be used after the user
+ * successfully authenticates to one of the associated user authenticators.
+ *
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
+ * is required for every use of the key.
+ */
+ public int getUserAuthenticationValidityDurationSeconds() {
+ return mUserAuthenticationValidityDurationSeconds;
+ }
+}
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 2428c2a..4909467 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -18,12 +18,10 @@
import android.content.Context;
+import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyStore.ProtectionParameter;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
/**
* This provides the optional parameters that can be specified for
@@ -50,29 +48,25 @@
private final Date mKeyValidityStart;
private final Date mKeyValidityForOriginationEnd;
private final Date mKeyValidityForConsumptionEnd;
- private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
- private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
- private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
- private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
- private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
- private final Integer mMinSecondsBetweenOperations;
- private final Integer mMaxUsesPerBoot;
- private final Set<Integer> mUserAuthenticators;
- private final Integer mUserAuthenticationValidityDurationSeconds;
+ private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
+ private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private final int mUserAuthenticationValidityDurationSeconds;
- private KeyStoreParameter(int flags, Date keyValidityStart,
- Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd,
- @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
- @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm,
- @KeyStoreKeyConstraints.PaddingEnum Integer padding,
- @KeyStoreKeyConstraints.DigestEnum Integer digest,
- @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
- Integer minSecondsBetweenOperations,
- Integer maxUsesPerBoot,
- Set<Integer> userAuthenticators,
- Integer userAuthenticationValidityDurationSeconds) {
- if ((userAuthenticationValidityDurationSeconds != null)
- && (userAuthenticationValidityDurationSeconds < 0)) {
+ private KeyStoreParameter(int flags,
+ Date keyValidityStart,
+ Date keyValidityForOriginationEnd,
+ Date keyValidityForConsumptionEnd,
+ @KeyStoreKeyConstraints.PurposeEnum int purposes,
+ @KeyStoreKeyConstraints.PaddingEnum int paddings,
+ @KeyStoreKeyConstraints.DigestEnum Integer digests,
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+ int userAuthenticationValidityDurationSeconds) {
+ if ((userAuthenticationValidityDurationSeconds < 0)
+ && (userAuthenticationValidityDurationSeconds != -1)) {
throw new IllegalArgumentException(
"userAuthenticationValidityDurationSeconds must not be negative");
}
@@ -82,15 +76,10 @@
mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
mPurposes = purposes;
- mAlgorithm = algorithm;
- mPadding = padding;
- mDigest = digest;
- mBlockMode = blockMode;
- mMinSecondsBetweenOperations = minSecondsBetweenOperations;
- mMaxUsesPerBoot = maxUsesPerBoot;
- mUserAuthenticators = (userAuthenticators != null)
- ? new HashSet<Integer>(userAuthenticators)
- : Collections.<Integer>emptySet();
+ mPaddings = paddings;
+ mDigests = digests;
+ mBlockModes = blockModes;
+ mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
}
@@ -142,105 +131,81 @@
}
/**
- * Gets the set of purposes for which the key can be used to the provided set of purposes.
- *
- * @return set of purposes or {@code null} if the key can be used for any purpose.
+ * Gets the set of purposes for which the key can be used.
*
* @hide
*/
- public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+ public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
return mPurposes;
}
/**
- * Gets the algorithm to which the key is restricted.
+ * Gets the set of padding schemes to which the key is restricted.
*
- * @return algorithm or {@code null} if it's not restricted.
* @hide
*/
- public @KeyStoreKeyConstraints.AlgorithmEnum Integer getAlgorithm() {
- return mAlgorithm;
+ public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+ return mPaddings;
}
/**
- * Gets the padding scheme to which the key is restricted.
+ * Gets the set of digests to which the key is restricted.
*
- * @return padding scheme or {@code null} if the padding scheme is not restricted.
+ * @throws IllegalStateException if this restriction has not been specified.
+ *
+ * @see #isDigestsSpecified()
*
* @hide
*/
- public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
- return mPadding;
+ public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+ if (mDigests == null) {
+ throw new IllegalStateException("Digests not specified");
+ }
+ return mDigests;
}
/**
- * Gets the digest to which the key is restricted when generating Message Authentication Codes
- * (MACs).
+ * Returns {@code true} if digest restrictions have been specified.
*
- * @return digest or {@code null} if the digest is not restricted.
+ * @see #getDigests()
*
* @hide
*/
- public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
- return mDigest;
+ public boolean isDigestsSpecified() {
+ return mDigests != null;
}
/**
- * Gets the block mode to which the key is restricted when used for encryption or decryption.
- *
- * @return block more or {@code null} if block mode is not restricted.
+ * Gets the set of block modes to which the key is restricted.
*
* @hide
*/
- public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
- return mBlockMode;
+ public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+ return mBlockModes;
}
/**
- * Gets the minimum number of seconds that must expire since the most recent use of the key
- * before it can be used again.
+ * Gets the set of user authenticators which protect access to this key. The key can only be
+ * used iff the user has authenticated to at least one of these user authenticators.
*
- * @return number of seconds or {@code null} if there is no restriction on how frequently a key
- * can be used.
+ * @return user authenticators or {@code 0} if the key can be used without user authentication.
*
* @hide
*/
- public Integer getMinSecondsBetweenOperations() {
- return mMinSecondsBetweenOperations;
- }
-
- /**
- * Gets the number of times the key can be used without rebooting the device.
- *
- * @return maximum number of times or {@code null} if there is no restriction.
- * @hide
- */
- public Integer getMaxUsesPerBoot() {
- return mMaxUsesPerBoot;
- }
-
- /**
- * Gets the user authenticators which protect access to this key. The key can only be used iff
- * the user has authenticated to at least one of these user authenticators.
- *
- * @return user authenticators or empty set if the key can be used without user authentication.
- *
- * @hide
- */
- public Set<Integer> getUserAuthenticators() {
- return new HashSet<Integer>(mUserAuthenticators);
+ public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+ return mUserAuthenticators;
}
/**
* Gets the duration of time (seconds) for which this key can be used after the user
* successfully authenticates to one of the associated user authenticators.
*
- * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
+ * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
* is required for every use of the key.
*
* @hide
*/
- public Integer getUserAuthenticationValidityDurationSeconds() {
+ public int getUserAuthenticationValidityDurationSeconds() {
return mUserAuthenticationValidityDurationSeconds;
}
@@ -266,15 +231,12 @@
private Date mKeyValidityStart;
private Date mKeyValidityForOriginationEnd;
private Date mKeyValidityForConsumptionEnd;
- private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
- private @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
- private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
- private @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
- private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
- private Integer mMinSecondsBetweenOperations;
- private Integer mMaxUsesPerBoot;
- private Set<Integer> mUserAuthenticators;
- private Integer mUserAuthenticationValidityDurationSeconds;
+ private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+ private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+ private @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
+ private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+ private int mUserAuthenticationValidityDurationSeconds = -1;
/**
* Creates a new instance of the {@code Builder} with the given
@@ -366,9 +328,9 @@
}
/**
- * Restricts the purposes for which the key can be used to the provided set of purposes.
+ * Restricts the key to being used only for the provided set of purposes.
*
- * <p>By default, the key can be used for encryption, decryption, signing, and verification.
+ * <p>This restriction must be specified. There is no default.
*
* @hide
*/
@@ -378,83 +340,43 @@
}
/**
- * Sets the algorithm of the key.
- *
- * <p>The algorithm of symmetric keys can be deduced from the key itself. Thus, explicitly
- * specifying the algorithm of symmetric keys using this method is not necessary.
- *
- * @hide
- */
- public Builder setAlgorithm(@KeyStoreKeyConstraints.AlgorithmEnum int algorithm) {
- mAlgorithm = algorithm;
- return this;
- }
-
- /**
- * Restricts the key to being used only with the provided padding scheme. Attempts to use
+ * Restricts the key to being used only with the provided padding schemes. Attempts to use
* the key with any other padding will be rejected.
*
* <p>This restriction must be specified for keys which are used for encryption/decryption.
*
* @hide
*/
- public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
- mPadding = padding;
+ public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+ mPaddings = paddings;
return this;
}
/**
- * Restricts the key to being used only with the provided digest when generating Message
- * Authentication Codes (MACs). Attempts to use the key with any other digest will be
- * rejected.
+ * Restricts the key to being used only with the provided digests when generating signatures
+ * or HMACs. Attempts to use the key with any other digest will be rejected.
*
- * <p>For MAC keys, the default is to restrict to the digest specified in the key algorithm
- * name.
- *
- * @see java.security.Key#getAlgorithm()
+ * <p>For HMAC keys, the default is to restrict to the digest specified in
+ * {@link Key#getAlgorithm()}. For asymmetric signing keys this constraint must be specified
+ * because there is no default.
*
* @hide
*/
- public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) {
- mDigest = digest;
+ public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) {
+ mDigests = digests;
return this;
}
/**
- * Restricts the key to being used only with the provided block mode when encrypting or
- * decrypting. Attempts to use the key with any other block modes will be rejected.
+ * Restricts the key to being used only with the provided block modes. Attempts to use the
+ * key with any other block modes will be rejected.
*
- * <p>This restriction must be specified for keys which are used for encryption/decryption.
+ * <p>This restriction must be specified for symmetric encryption/decryption keys.
*
* @hide
*/
- public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
- mBlockMode = blockMode;
- return this;
- }
-
- /**
- * Sets the minimum number of seconds that must expire since the most recent use of the key
- * before it can be used again.
- *
- * <p>By default, there is no restriction on how frequently a key can be used.
- *
- * @hide
- */
- public Builder setMinSecondsBetweenOperations(int seconds) {
- mMinSecondsBetweenOperations = seconds;
- return this;
- }
-
- /**
- * Sets the maximum number of times a key can be used without rebooting the device.
- *
- * <p>By default, the key can be used for an unlimited number of times.
- *
- * @hide
- */
- public Builder setMaxUsesPerBoot(int count) {
- mMaxUsesPerBoot = count;
+ public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+ mBlockModes = blockModes;
return this;
}
@@ -464,16 +386,16 @@
*
* <p>By default, the key can be used without user authentication.
*
- * @param userAuthenticators user authenticators or empty list if this key can be accessed
+ * @param userAuthenticators user authenticators or {@code 0} if this key can be accessed
* without user authentication.
*
* @see #setUserAuthenticationValidityDurationSeconds(int)
*
* @hide
*/
- public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
- mUserAuthenticators =
- (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
+ public Builder setUserAuthenticators(
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+ mUserAuthenticators = userAuthenticators;
return this;
}
@@ -486,7 +408,7 @@
* @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
* every use of the key.
*
- * @see #setUserAuthenticators(Set)
+ * @see #setUserAuthenticators(int)
*
* @hide
*/
@@ -502,10 +424,15 @@
* @return built instance of {@code KeyStoreParameter}
*/
public KeyStoreParameter build() {
- return new KeyStoreParameter(mFlags, mKeyValidityStart,
- mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, mPurposes,
- mAlgorithm, mPadding, mDigest, mBlockMode, mMinSecondsBetweenOperations,
- mMaxUsesPerBoot, mUserAuthenticators,
+ return new KeyStoreParameter(mFlags,
+ mKeyValidityStart,
+ mKeyValidityForOriginationEnd,
+ mKeyValidityForConsumptionEnd,
+ mPurposes,
+ mPaddings,
+ mDigests,
+ mBlockModes,
+ mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds);
}
}
diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/KeyStoreSecretKey.java
index 9410127..7f0e3d3 100644
--- a/keystore/java/android/security/KeyStoreSecretKey.java
+++ b/keystore/java/android/security/KeyStoreSecretKey.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.security;
import javax.crypto.SecretKey;
diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
new file mode 100644
index 0000000..09f0b00
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.InvalidKeyException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Date;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactorySpi;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link SecretKeyFactorySpi} backed by Android KeyStore.
+ *
+ * @hide
+ */
+public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ @Override
+ protected KeySpec engineGetKeySpec(SecretKey key,
+ @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
+ if (keySpecClass == null) {
+ throw new InvalidKeySpecException("keySpecClass == null");
+ }
+ if (!(key instanceof KeyStoreSecretKey)) {
+ throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " +
+ ((key != null) ? key.getClass().getName() : "null"));
+ }
+ if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) {
+ throw new InvalidKeySpecException(
+ "Key material export of Android KeyStore keys is not supported");
+ }
+ if (!KeyStoreKeySpec.class.equals(keySpecClass)) {
+ throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
+ }
+ String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias();
+ String entryAlias;
+ if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) {
+ entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length());
+ } else {
+ throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore);
+ }
+
+ KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
+ int errorCode =
+ mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new InvalidKeySpecException("Failed to obtain information about key."
+ + " Keystore error: " + errorCode);
+ }
+
+ @KeyStoreKeyCharacteristics.OriginEnum Integer origin;
+ int keySize;
+ @KeyStoreKeyConstraints.PurposeEnum int purposes;
+ @KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
+ @KeyStoreKeyConstraints.PaddingEnum int paddings;
+ @KeyStoreKeyConstraints.DigestEnum int digests;
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes;
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators;
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
+ try {
+ origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN);
+ if (origin == null) {
+ throw new InvalidKeySpecException("Key origin not available");
+ }
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin);
+ Integer keySizeInteger =
+ KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE);
+ if (keySizeInteger == null) {
+ throw new InvalidKeySpecException("Key size not available");
+ }
+ keySize = keySizeInteger;
+ purposes = KeyStoreKeyConstraints.Purpose.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PURPOSE));
+ Integer alg = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ALGORITHM);
+ if (alg == null) {
+ throw new InvalidKeySpecException("Key algorithm not available");
+ }
+ algorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(alg);
+ paddings = KeyStoreKeyConstraints.Padding.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING));
+ digests = KeyStoreKeyConstraints.Digest.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST));
+ blockModes = KeyStoreKeyConstraints.BlockMode.allFromKeymaster(
+ KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE));
+
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum
+ int swEnforcedKeymasterUserAuthenticators =
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum
+ int hwEnforcedKeymasterUserAuthenticators =
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+ @KeyStoreKeyConstraints.UserAuthenticatorEnum
+ int keymasterUserAuthenticators =
+ swEnforcedKeymasterUserAuthenticators | hwEnforcedKeymasterUserAuthenticators;
+ userAuthenticators = KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
+ keymasterUserAuthenticators);
+ teeEnforcedUserAuthenticators =
+ KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
+ hwEnforcedKeymasterUserAuthenticators);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidKeySpecException("Unsupported key characteristic", e);
+ }
+
+ Date keyValidityStart =
+ KeymasterUtils.getDate(keyCharacteristics, KeymasterDefs.KM_TAG_ACTIVE_DATETIME);
+ if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) {
+ keyValidityStart = null;
+ }
+ Date keyValidityForOriginationEnd = KeymasterUtils.getDate(keyCharacteristics,
+ KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME);
+ if ((keyValidityForOriginationEnd != null)
+ && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) {
+ keyValidityForOriginationEnd = null;
+ }
+ Date keyValidityForConsumptionEnd = KeymasterUtils.getDate(keyCharacteristics,
+ KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME);
+ if ((keyValidityForConsumptionEnd != null)
+ && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) {
+ keyValidityForConsumptionEnd = null;
+ }
+ Integer userAuthenticationValidityDurationSeconds =
+ KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT);
+
+ return new KeyStoreKeySpec(entryAlias,
+ origin,
+ keySize,
+ keyValidityStart,
+ keyValidityForOriginationEnd,
+ keyValidityForConsumptionEnd,
+ purposes,
+ algorithm,
+ paddings,
+ digests,
+ blockModes,
+ userAuthenticators,
+ teeEnforcedUserAuthenticators,
+ ((userAuthenticationValidityDurationSeconds != null)
+ ? userAuthenticationValidityDurationSeconds : -1));
+ }
+
+ @Override
+ protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
+ throw new UnsupportedOperationException(
+ "Key import into Android KeyStore is not supported");
+ }
+
+ @Override
+ protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
+ throw new UnsupportedOperationException(
+ "Key import into Android KeyStore is not supported");
+ }
+}
diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java
new file mode 100644
index 0000000..3143d4d
--- /dev/null
+++ b/keystore/java/android/security/KeymasterUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.security.keymaster.KeyCharacteristics;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public abstract class KeymasterUtils {
+ private KeymasterUtils() {}
+
+ public static Integer getInt(KeyCharacteristics keyCharacteristics, int tag) {
+ if (keyCharacteristics.hwEnforced.containsTag(tag)) {
+ return keyCharacteristics.hwEnforced.getInt(tag, -1);
+ } else if (keyCharacteristics.swEnforced.containsTag(tag)) {
+ return keyCharacteristics.swEnforced.getInt(tag, -1);
+ } else {
+ return null;
+ }
+ }
+
+ public static List<Integer> getInts(KeyCharacteristics keyCharacteristics, int tag) {
+ List<Integer> result = new ArrayList<Integer>();
+ result.addAll(keyCharacteristics.hwEnforced.getInts(tag));
+ result.addAll(keyCharacteristics.swEnforced.getInts(tag));
+ return result;
+ }
+
+ public static Date getDate(KeyCharacteristics keyCharacteristics, int tag) {
+ Date result = keyCharacteristics.hwEnforced.getDate(tag, null);
+ if (result == null) {
+ result = keyCharacteristics.swEnforced.getDate(tag, null);
+ }
+ return result;
+ }
+
+ public static boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) {
+ if (keyCharacteristics.hwEnforced.containsTag(tag)) {
+ return keyCharacteristics.hwEnforced.getBoolean(tag, false);
+ } else {
+ return keyCharacteristics.swEnforced.getBoolean(tag, false);
+ }
+ }
+}
diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/UserNotAuthenticatedException.java
new file mode 100644
index 0000000..e6342ef
--- /dev/null
+++ b/keystore/java/android/security/UserNotAuthenticatedException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+/**
+ * Indicates that a cryptographic operation could not be performed because the user has not been
+ * authenticated recently enough.
+ *
+ * @hide
+ */
+public class UserNotAuthenticatedException extends CryptoOperationException {
+
+ /**
+ * Constructs a new {@code UserNotAuthenticatedException} without detail message and cause.
+ */
+ public UserNotAuthenticatedException() {
+ super("User not authenticated");
+ }
+
+ /**
+ * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and
+ * no cause.
+ */
+ public UserNotAuthenticatedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and
+ * cause.
+ */
+ public UserNotAuthenticatedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 7468fb5e..c9a140c 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -712,6 +712,7 @@
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
RSAKeyGenParameterSpec.F4.longValue());
@@ -735,6 +736,7 @@
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
RSAKeyGenParameterSpec.F4.longValue());
@@ -769,6 +771,7 @@
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
args.addBlob(KeymasterDefs.KM_TAG_APPLICATION_ID, id);
args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
RSAKeyGenParameterSpec.F4.longValue());
@@ -806,6 +809,7 @@
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
@@ -838,6 +842,7 @@
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mode);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, size);
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
return mKeyStore.importKey(name, args, KeymasterDefs.KM_KEY_FORMAT_RAW, key, 0,
new KeyCharacteristics());
}
@@ -901,6 +906,7 @@
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
@@ -922,4 +928,30 @@
assertEquals("Operation should be pruned", KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE,
mKeyStore.update(first, null, new byte[] {0x01}).resultCode);
}
+
+ public void testAuthNeeded() throws Exception {
+ String name = "test";
+ KeymasterArguments args = new KeymasterArguments();
+ args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
+ args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
+ args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
+ args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
+ args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
+
+ KeyCharacteristics outCharacteristics = new KeyCharacteristics();
+ int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
+ KeymasterArguments out = new KeymasterArguments();
+ assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
+ OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
+ true, args, null, out);
+ assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
+ IBinder token = result.token;
+ result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
+ assertEquals("Update should require authorization",
+ KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED, result.resultCode);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e66934e..fae0643 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -773,6 +773,11 @@
synchronized (this) {
if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
+ if (isSecure()) {
+ Log.d(TAG, "current mode is SecurityMode, ignore hide keyguard");
+ return;
+ }
+
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
diff --git a/preloaded-classes b/preloaded-classes
index c8d8c5d..151766f 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1146,8 +1146,6 @@
android.provider.Settings$System
android.provider.Telephony$Mms
android.renderscript.RenderScript
-android.security.AndroidKeyPairGenerator
-android.security.AndroidKeyStore
android.security.AndroidKeyStoreProvider
android.speech.tts.TextToSpeechService
android.speech.tts.TextToSpeechService$SpeechItemV1
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index 523c8fb..28547cc 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -1340,15 +1340,22 @@
private void copyTo(Object array, Element.DataType dt, int arrayLen) {
Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo");
- if (dt.mSize * arrayLen < mSize) {
- throw new RSIllegalArgumentException(
- "Size of output array cannot be smaller than size of allocation.");
- }
mRS.validate();
boolean usePadding = false;
if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) {
usePadding = true;
}
+ if (usePadding) {
+ if (dt.mSize * arrayLen < mSize / 4 * 3) {
+ throw new RSIllegalArgumentException(
+ "Size of output array cannot be smaller than size of allocation.");
+ }
+ } else {
+ if (dt.mSize * arrayLen < mSize) {
+ throw new RSIllegalArgumentException(
+ "Size of output array cannot be smaller than size of allocation.");
+ }
+ }
mRS.nAllocationRead(getID(mRS), array, dt, mType.mElement.mType.mSize, usePadding);
Trace.traceEnd(RenderScript.TRACE_TAG);
}
diff --git a/rs/java/android/renderscript/FieldPacker.java b/rs/java/android/renderscript/FieldPacker.java
index 0f967fc..de1c497 100644
--- a/rs/java/android/renderscript/FieldPacker.java
+++ b/rs/java/android/renderscript/FieldPacker.java
@@ -47,6 +47,15 @@
// subAlign() can never work correctly for copied FieldPacker objects.
}
+ static FieldPacker createFromArray(Object[] args) {
+ FieldPacker fp = new FieldPacker(RenderScript.sPointerSize * 8);
+ for (Object arg : args) {
+ fp.addSafely(arg);
+ }
+ fp.resize(fp.mPos);
+ return fp;
+ }
+
public void align(int v) {
if ((v <= 0) || ((v & (v - 1)) != 0)) {
throw new RSIllegalArgumentException("argument must be a non-negative non-zero power of 2: " + v);
@@ -618,294 +627,182 @@
return mPos;
}
- private static void addToPack(FieldPacker fp, Object obj) {
+ private void add(Object obj) {
if (obj instanceof Boolean) {
- fp.addBoolean(((Boolean)obj).booleanValue());
+ addBoolean((Boolean)obj);
return;
}
if (obj instanceof Byte) {
- fp.addI8(((Byte)obj).byteValue());
+ addI8((Byte)obj);
return;
}
if (obj instanceof Short) {
- fp.addI16(((Short)obj).shortValue());
+ addI16((Short)obj);
return;
}
if (obj instanceof Integer) {
- fp.addI32(((Integer)obj).intValue());
+ addI32((Integer)obj);
return;
}
if (obj instanceof Long) {
- fp.addI64(((Long)obj).longValue());
+ addI64((Long)obj);
return;
}
if (obj instanceof Float) {
- fp.addF32(((Float)obj).floatValue());
+ addF32((Float)obj);
return;
}
if (obj instanceof Double) {
- fp.addF64(((Double)obj).doubleValue());
+ addF64((Double)obj);
return;
}
if (obj instanceof Byte2) {
- fp.addI8((Byte2)obj);
+ addI8((Byte2)obj);
return;
}
if (obj instanceof Byte3) {
- fp.addI8((Byte3)obj);
+ addI8((Byte3)obj);
return;
}
if (obj instanceof Byte4) {
- fp.addI8((Byte4)obj);
+ addI8((Byte4)obj);
return;
}
if (obj instanceof Short2) {
- fp.addI16((Short2)obj);
+ addI16((Short2)obj);
return;
}
if (obj instanceof Short3) {
- fp.addI16((Short3)obj);
+ addI16((Short3)obj);
return;
}
if (obj instanceof Short4) {
- fp.addI16((Short4)obj);
+ addI16((Short4)obj);
return;
}
if (obj instanceof Int2) {
- fp.addI32((Int2)obj);
+ addI32((Int2)obj);
return;
}
if (obj instanceof Int3) {
- fp.addI32((Int3)obj);
+ addI32((Int3)obj);
return;
}
if (obj instanceof Int4) {
- fp.addI32((Int4)obj);
+ addI32((Int4)obj);
return;
}
if (obj instanceof Long2) {
- fp.addI64((Long2)obj);
+ addI64((Long2)obj);
return;
}
if (obj instanceof Long3) {
- fp.addI64((Long3)obj);
+ addI64((Long3)obj);
return;
}
if (obj instanceof Long4) {
- fp.addI64((Long4)obj);
+ addI64((Long4)obj);
return;
}
if (obj instanceof Float2) {
- fp.addF32((Float2)obj);
+ addF32((Float2)obj);
return;
}
if (obj instanceof Float3) {
- fp.addF32((Float3)obj);
+ addF32((Float3)obj);
return;
}
if (obj instanceof Float4) {
- fp.addF32((Float4)obj);
+ addF32((Float4)obj);
return;
}
if (obj instanceof Double2) {
- fp.addF64((Double2)obj);
+ addF64((Double2)obj);
return;
}
if (obj instanceof Double3) {
- fp.addF64((Double3)obj);
+ addF64((Double3)obj);
return;
}
if (obj instanceof Double4) {
- fp.addF64((Double4)obj);
+ addF64((Double4)obj);
return;
}
if (obj instanceof Matrix2f) {
- fp.addMatrix((Matrix2f)obj);
+ addMatrix((Matrix2f)obj);
return;
}
if (obj instanceof Matrix3f) {
- fp.addMatrix((Matrix3f)obj);
+ addMatrix((Matrix3f)obj);
return;
}
if (obj instanceof Matrix4f) {
- fp.addMatrix((Matrix4f)obj);
+ addMatrix((Matrix4f)obj);
return;
}
if (obj instanceof BaseObj) {
- fp.addObj((BaseObj)obj);
+ addObj((BaseObj)obj);
return;
}
}
- private static int getPackedSize(Object obj) {
- if (obj instanceof Boolean) {
- return 1;
+ private boolean resize(int newSize) {
+ if (newSize == mLen) {
+ return false;
}
- if (obj instanceof Byte) {
- return 1;
- }
+ byte[] newData = new byte[newSize];
+ System.arraycopy(mData, 0, newData, 0, mPos);
+ mData = newData;
+ mLen = newSize;
+ return true;
+ }
- if (obj instanceof Short) {
- return 2;
- }
-
- if (obj instanceof Integer) {
- return 4;
- }
-
- if (obj instanceof Long) {
- return 8;
- }
-
- if (obj instanceof Float) {
- return 4;
- }
-
- if (obj instanceof Double) {
- return 8;
- }
-
- if (obj instanceof Byte2) {
- return 2;
- }
-
- if (obj instanceof Byte3) {
- return 3;
- }
-
- if (obj instanceof Byte4) {
- return 4;
- }
-
- if (obj instanceof Short2) {
- return 4;
- }
-
- if (obj instanceof Short3) {
- return 6;
- }
-
- if (obj instanceof Short4) {
- return 8;
- }
-
- if (obj instanceof Int2) {
- return 8;
- }
-
- if (obj instanceof Int3) {
- return 12;
- }
-
- if (obj instanceof Int4) {
- return 16;
- }
-
- if (obj instanceof Long2) {
- return 16;
- }
-
- if (obj instanceof Long3) {
- return 24;
- }
-
- if (obj instanceof Long4) {
- return 32;
- }
-
- if (obj instanceof Float2) {
- return 8;
- }
-
- if (obj instanceof Float3) {
- return 12;
- }
-
- if (obj instanceof Float4) {
- return 16;
- }
-
- if (obj instanceof Double2) {
- return 16;
- }
-
- if (obj instanceof Double3) {
- return 24;
- }
-
- if (obj instanceof Double4) {
- return 32;
- }
-
- if (obj instanceof Matrix2f) {
- return 16;
- }
-
- if (obj instanceof Matrix3f) {
- return 36;
- }
-
- if (obj instanceof Matrix4f) {
- return 64;
- }
-
- if (obj instanceof BaseObj) {
- if (RenderScript.sPointerSize == 8) {
- return 32;
- } else {
- return 4;
+ private void addSafely(Object obj) {
+ boolean retry;
+ final int oldPos = mPos;
+ do {
+ retry = false;
+ try {
+ add(obj);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ mPos = oldPos;
+ resize(mLen * 2);
+ retry = true;
}
- }
-
- return 0;
+ } while (retry);
}
- static FieldPacker createFieldPack(Object[] args) {
- int len = 0;
- for (Object arg : args) {
- len += getPackedSize(arg);
- }
- FieldPacker fp = new FieldPacker(len);
- for (Object arg : args) {
- addToPack(fp, arg);
- }
- return fp;
- }
-
- private final byte mData[];
+ private byte mData[];
private int mPos;
private int mLen;
private BitSet mAlignment;
-
}
-
-
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 45f0ca6..5138719 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,7 +16,6 @@
package android.renderscript;
-import java.io.File;
import java.lang.reflect.Method;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -130,8 +129,6 @@
native void nContextInitToClient(long con);
native void nContextDeinitToClient(long con);
- static File mCacheDir;
-
// this should be a monotonically increasing ID
// used in conjunction with the API version of a device
static final long sMinorID = 1;
@@ -146,23 +143,6 @@
return sMinorID;
}
- /**
- * Sets the directory to use as a persistent storage for the
- * renderscript object file cache.
- *
- * @hide
- * @param cacheDir A directory the current process can write to
- */
- public static void setupDiskCache(File cacheDir) {
- if (!sInitialized) {
- Log.e(LOG_TAG, "RenderScript.setupDiskCache() called when disabled");
- return;
- }
-
- // Defer creation of cache path to nScriptCCreate().
- mCacheDir = cacheDir;
- }
-
/**
* ContextType specifies the specific type of context to be created.
*
diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java
new file mode 100644
index 0000000..95a9d75
--- /dev/null
+++ b/rs/java/android/renderscript/RenderScriptCacheDir.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008-2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.renderscript;
+
+import java.io.File;
+
+/**
+ * Used only for tracking the RenderScript cache directory.
+ * @hide
+ */
+public class RenderScriptCacheDir {
+ /**
+ * Sets the directory to use as a persistent storage for the
+ * renderscript object file cache.
+ *
+ * @hide
+ * @param cacheDir A directory the current process can write to
+ */
+ public static void setupDiskCache(File cacheDir) {
+ // Defer creation of cache path to nScriptCCreate().
+ mCacheDir = cacheDir;
+ }
+
+ static File mCacheDir;
+
+}
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 64d21e4..bf706c1 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -124,7 +124,7 @@
// Create the RS cache path if we haven't done so already.
if (mCachePath == null) {
- File f = new File(rs.mCacheDir, CACHE_PATH);
+ File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
mCachePath = f.getAbsolutePath();
f.mkdirs();
}
@@ -135,7 +135,7 @@
private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
// Create the RS cache path if we haven't done so already.
if (mCachePath == null) {
- File f = new File(rs.mCacheDir, CACHE_PATH);
+ File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
mCachePath = f.getAbsolutePath();
f.mkdirs();
}
diff --git a/rs/java/android/renderscript/ScriptGroup2.java b/rs/java/android/renderscript/ScriptGroup2.java
index 4a56572..857e9fb 100644
--- a/rs/java/android/renderscript/ScriptGroup2.java
+++ b/rs/java/android/renderscript/ScriptGroup2.java
@@ -44,6 +44,7 @@
public class ScriptGroup2 extends BaseObj {
public static class Closure extends BaseObj {
+ private Object[] mArgs;
private Allocation mReturnValue;
private Map<Script.FieldID, Object> mBindings;
@@ -62,8 +63,9 @@
Object[] args, Map<Script.FieldID, Object> globals) {
super(0, rs);
+ mArgs = args;
mReturnValue = Allocation.createTyped(rs, returnType);
- mBindings = new HashMap<Script.FieldID, Object>();
+ mBindings = globals;
mGlobalFuture = new HashMap<Script.FieldID, Future>();
int numValues = args.length + globals.size();
@@ -110,9 +112,10 @@
public Closure(RenderScript rs, Script.InvokeID invokeID,
Object[] args, Map<Script.FieldID, Object> globals) {
super(0, rs);
- mFP = FieldPacker.createFieldPack(args);
+ mFP = FieldPacker.createFromArray(args);
- mBindings = new HashMap<Script.FieldID, Object>();
+ mArgs = args;
+ mBindings = globals;
mGlobalFuture = new HashMap<Script.FieldID, Future>();
int numValues = globals.size();
@@ -198,11 +201,13 @@
}
void setArg(int index, Object obj) {
+ mArgs[index] = obj;
ValueAndSize vs = new ValueAndSize(mRS, obj);
mRS.nClosureSetArg(getID(mRS), index, vs.value, vs.size);
}
void setGlobal(Script.FieldID fieldID, Object obj) {
+ mBindings.put(fieldID, obj);
ValueAndSize vs = new ValueAndSize(mRS, obj);
mRS.nClosureSetGlobal(getID(mRS), fieldID.getID(mRS), vs.value, vs.size);
}
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlur.java b/rs/java/android/renderscript/ScriptIntrinsicBlur.java
index 5c4edd3..60e2b6d9 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBlur.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBlur.java
@@ -34,7 +34,7 @@
* Create an intrinsic for applying a blur to an allocation. The
* default radius is 5.0.
*
- * Supported elements types are {@link Element#U8_4}
+ * Supported elements types are {@link Element#U8_4 Element#U8}
*
* @param rs The RenderScript context
* @param e Element type for inputs and outputs
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index ba20881..3591199 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -372,7 +372,7 @@
return (jlong)(uintptr_t)rsClosureCreate(
(RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
fieldIDs, (size_t)fieldIDs_length, values, (size_t)values_length,
- (size_t*)sizes, (size_t)sizes_length,
+ (int*)sizes, (size_t)sizes_length,
depClosures, (size_t)depClosures_length,
depFieldIDs, (size_t)depFieldIDs_length);
}
@@ -405,7 +405,7 @@
return (jlong)(uintptr_t)rsInvokeClosureCreate(
(RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
fieldIDs, (size_t)fieldIDs_length, values, (size_t)values_length,
- (size_t*)sizes, (size_t)sizes_length);
+ (int*)sizes, (size_t)sizes_length);
}
static void
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 5c45201..43249e7 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -10,5 +10,6 @@
java/com/android/server/am/EventLogTags.logtags
LOCAL_JAVA_LIBRARIES := android.policy telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d9ef766..758b0fc 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2022,6 +2022,8 @@
ReapUnvalidatedNetworks.DONT_REAP);
}
}
+ NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
+ if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
}
// If this method proves to be too slow then we can maintain a separate
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 059dde1..4e7aa77 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2127,8 +2127,16 @@
}
}
- // First clear app state from services.
- for (int i=app.services.size()-1; i>=0; i--) {
+ // Clean up any connections this application has to other services.
+ for (int i = app.connections.size() - 1; i >= 0; i--) {
+ ConnectionRecord r = app.connections.valueAt(i);
+ removeConnectionLocked(r, app, null);
+ }
+ updateServiceConnectionActivitiesLocked(app);
+ app.connections.clear();
+
+ // Clear app state from services.
+ for (int i = app.services.size() - 1; i >= 0; i--) {
ServiceRecord sr = app.services.valueAt(i);
synchronized (sr.stats.getBatteryStats()) {
sr.stats.stopLaunchedLocked();
@@ -2188,14 +2196,6 @@
}
}
- // Clean up any connections this application has to other services.
- for (int i=app.connections.size()-1; i>=0; i--) {
- ConnectionRecord r = app.connections.valueAt(i);
- removeConnectionLocked(r, app, null);
- }
- updateServiceConnectionActivitiesLocked(app);
- app.connections.clear();
-
ServiceMap smap = getServiceMap(app.userId);
// Now do remaining service cleanup.
@@ -2228,7 +2228,7 @@
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
sr.userId, sr.crashCount, sr.shortName, app.pid);
bringDownServiceLocked(sr);
- } else if (!allowRestart) {
+ } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) {
bringDownServiceLocked(sr);
} else {
boolean canceled = scheduleServiceRestartLocked(sr, true);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ab1a1e8..09ebe60 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -169,6 +169,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IPermissionController;
+import android.os.IProcessInfoService;
import android.os.IRemoteCallback;
import android.os.IUserManager;
import android.os.Looper;
@@ -1918,6 +1919,7 @@
ServiceManager.addService("cpuinfo", new CpuBinder(this));
}
ServiceManager.addService("permission", new PermissionController(this));
+ ServiceManager.addService("processinfo", new ProcessInfoService(this));
ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
"android", STOCK_PM_FLAGS);
@@ -2787,10 +2789,38 @@
if (!isolated) {
app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
checkTime(startTime, "startProcess: after getProcessRecord");
+
+ if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
+ // If we are in the background, then check to see if this process
+ // is bad. If so, we will just silently fail.
+ if (mBadProcesses.get(info.processName, info.uid) != null) {
+ if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
+ + "/" + info.processName);
+ return null;
+ }
+ } else {
+ // When the user is explicitly starting a process, then clear its
+ // crash count so that we won't make it bad until they see at
+ // least one crash dialog again, and make the process good again
+ // if it had been bad.
+ if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
+ + "/" + info.processName);
+ mProcessCrashTimes.remove(info.processName, info.uid);
+ if (mBadProcesses.get(info.processName, info.uid) != null) {
+ EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
+ UserHandle.getUserId(info.uid), info.uid,
+ info.processName);
+ mBadProcesses.remove(info.processName, info.uid);
+ if (app != null) {
+ app.bad = false;
+ }
+ }
+ }
} else {
// If this is an isolated process, it can't re-use an existing process.
app = null;
}
+
// We don't have to do anything more if:
// (1) There is an existing application record; and
// (2) The caller doesn't think it is dead, OR there is no thread
@@ -2824,35 +2854,6 @@
String hostingNameStr = hostingName != null
? hostingName.flattenToShortString() : null;
- if (!isolated) {
- if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) {
- // If we are in the background, then check to see if this process
- // is bad. If so, we will just silently fail.
- if (mBadProcesses.get(info.processName, info.uid) != null) {
- if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
- + "/" + info.processName);
- return null;
- }
- } else {
- // When the user is explicitly starting a process, then clear its
- // crash count so that we won't make it bad until they see at
- // least one crash dialog again, and make the process good again
- // if it had been bad.
- if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
- + "/" + info.processName);
- mProcessCrashTimes.remove(info.processName, info.uid);
- if (mBadProcesses.get(info.processName, info.uid) != null) {
- EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
- UserHandle.getUserId(info.uid), info.uid,
- info.processName);
- mBadProcesses.remove(info.processName, info.uid);
- if (app != null) {
- app.bad = false;
- }
- }
- }
- }
-
if (app == null) {
checkTime(startTime, "startProcess: creating new process record");
app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
@@ -4590,17 +4591,13 @@
finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);
}
- if (!restarting) {
- if (!mStackSupervisor.resumeTopActivitiesLocked()) {
- // If there was nothing to resume, and we are not already
- // restarting this process, but there is a visible activity that
- // is hosted by the process... then make sure all visible
- // activities are running, taking care of restarting this
- // process.
- if (hasVisibleActivities) {
- mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
- }
- }
+ if (!restarting && hasVisibleActivities && !mStackSupervisor.resumeTopActivitiesLocked()) {
+ // If there was nothing to resume, and we are not already
+ // restarting this process, but there is a visible activity that
+ // is hosted by the process... then make sure all visible
+ // activities are running, taking care of restarting this
+ // process.
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
}
}
@@ -6805,7 +6802,46 @@
}
}
}
-
+
+ // =========================================================
+ // PROCESS INFO
+ // =========================================================
+
+ static class ProcessInfoService extends IProcessInfoService.Stub {
+ final ActivityManagerService mActivityManagerService;
+ ProcessInfoService(ActivityManagerService activityManagerService) {
+ mActivityManagerService = activityManagerService;
+ }
+
+ @Override
+ public void getProcessStatesFromPids(/*in*/ int[] pids, /*out*/ int[] states) {
+ mActivityManagerService.getProcessStatesForPIDs(/*in*/ pids, /*out*/ states);
+ }
+ }
+
+ /**
+ * For each PID in the given input array, write the current process state
+ * for that process into the output array, or -1 to indicate that no
+ * process with the given PID exists.
+ */
+ public void getProcessStatesForPIDs(/*in*/ int[] pids, /*out*/ int[] states) {
+ if (pids == null) {
+ throw new NullPointerException("pids");
+ } else if (states == null) {
+ throw new NullPointerException("states");
+ } else if (pids.length != states.length) {
+ throw new IllegalArgumentException("input and output arrays have different lengths!");
+ }
+
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < pids.length; i++) {
+ ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
+ states[i] = (pr == null) ? ActivityManager.PROCESS_STATE_NONEXISTENT :
+ pr.curProcState;
+ }
+ }
+ }
+
// =========================================================
// PERMISSIONS
// =========================================================
@@ -17626,8 +17662,12 @@
mFullPssPending = true;
mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
mPendingPssProcesses.clear();
- for (int i=mLruProcesses.size()-1; i>=0; i--) {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
+ if (app.thread == null
+ || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ continue;
+ }
if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
@@ -17943,8 +17983,8 @@
}
}
}
- if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState,
- app.setProcState)) {
+ if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT
+ || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) {
if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
// Experimental code to more aggressively collect pss while
// running test... the problem is that this tends to collect
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b1b2a5c..1497c1d 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -982,24 +982,21 @@
}
private ActivityRecord getWaitingHistoryRecordLocked() {
- // First find the real culprit... if we are waiting
- // for another app to start, then we have paused dispatching
- // for this activity.
- ActivityRecord r = this;
- if (r.waitingVisible) {
+ // First find the real culprit... if this activity is waiting for
+ // another activity to start or has stopped, then the key dispatching
+ // timeout should not be caused by this.
+ if (waitingVisible || stopped) {
final ActivityStack stack = mStackSupervisor.getFocusedStack();
- // Hmmm, who might we be waiting for?
- r = stack.mResumedActivity;
+ // Try to use the one which is closest to top.
+ ActivityRecord r = stack.mResumedActivity;
if (r == null) {
r = stack.mPausingActivity;
}
- // Both of those null? Fall back to 'this' again
- if (r == null) {
- r = this;
+ if (r != null) {
+ return r;
}
}
-
- return r;
+ return this;
}
public boolean keyDispatchingTimedOut(String reason) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9f22aa9..ada16e7 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -899,6 +899,11 @@
r.userId, System.identityHashCode(r), r.shortComponentName,
mPausingActivity != null
? mPausingActivity.shortComponentName : "(none)");
+ if (r.finishing && r.state == ActivityState.PAUSING) {
+ if (DEBUG_PAUSE) Slog.v(TAG,
+ "Executing finish of failed to pause activity: " + r);
+ finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false);
+ }
}
}
}
@@ -3390,6 +3395,9 @@
if (DEBUG_CLEANUP) Slog.v(
TAG, "Record #" + i + " " + r + ": app=" + r.app);
if (r.app == app) {
+ if (r.visible) {
+ hasVisibleActivities = true;
+ }
boolean remove;
if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
// Don't currently have state for the activity, or
@@ -3431,9 +3439,6 @@
// it can be restarted later when needed.
if (localLOGV) Slog.v(
TAG, "Keeping entry, setting app to null");
- if (r.visible) {
- hasVisibleActivities = true;
- }
if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity "
+ r);
r.app = null;
@@ -3900,16 +3905,18 @@
}
void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) {
+ boolean focusedStack = mStackSupervisor.getFocusedStack() == this;
+ boolean topTask = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
+ if (task.getTopActivity() == null) {
+ continue;
+ }
ActivityRecord r = null;
ActivityRecord top = null;
int numActivities = 0;
int numRunning = 0;
final ArrayList<ActivityRecord> activities = task.mActivities;
- if (activities.isEmpty()) {
- continue;
- }
if (!allowed && !task.isHomeTask() && task.effectiveUid != callingUid) {
continue;
}
@@ -3938,14 +3945,18 @@
ci.baseActivity = r.intent.getComponent();
ci.topActivity = top.intent.getComponent();
ci.lastActiveTime = task.lastActiveTime;
+ if (focusedStack && topTask) {
+ // Give the latest time to ensure foreground task can be sorted
+ // at the first, because lastActiveTime of creating task is 0.
+ ci.lastActiveTime = System.currentTimeMillis();
+ topTask = false;
+ }
if (top.task != null) {
ci.description = top.task.lastDescription;
}
ci.numActivities = numActivities;
ci.numRunning = numRunning;
- //System.out.println(
- // "#" + maxNum + ": " + " descr=" + ci.description);
list.add(ci);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 27c5404..8ab2368 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -607,17 +607,21 @@
}
boolean allResumedActivitiesVisible() {
+ boolean foundResumed = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
final ActivityRecord r = stack.mResumedActivity;
- if (r != null && (!r.nowVisible || r.waitingVisible)) {
- return false;
+ if (r != null) {
+ if (!r.nowVisible || r.waitingVisible) {
+ return false;
+ }
+ foundResumed = true;
}
}
}
- return true;
+ return foundResumed;
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a6c616a..466831a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Slog;
@@ -83,10 +85,10 @@
int curSchedGroup; // Currently desired scheduling class
int setSchedGroup; // Last set to background scheduling class
int trimMemoryLevel; // Last selected memory trimming level
- int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_*
- int repProcState = -1; // Last reported process state
- int setProcState = -1; // Last set process state in process tracker
- int pssProcState = -1; // The proc state we are currently requesting pss for
+ int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
+ int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
+ int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
+ int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
boolean serviceb; // Process currently is on the service B list
boolean serviceHighRam; // We are forcing to service B list due to its RAM use
boolean setIsForeground; // Running foreground UI when last set?
@@ -418,7 +420,7 @@
tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
origBase.makeInactive();
}
- baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
+ baseProcessTracker = tracker.getProcessStateLocked(info.packageName, uid,
info.versionCode, processName);
baseProcessTracker.makeActive();
for (int i=0; i<pkgList.size(); i++) {
@@ -426,7 +428,7 @@
if (holder.state != null && holder.state != origBase) {
holder.state.makeInactive();
}
- holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+ holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), uid,
info.versionCode, processName);
if (holder.state != baseProcessTracker) {
holder.state.makeActive();
@@ -617,7 +619,7 @@
versionCode);
if (baseProcessTracker != null) {
holder.state = tracker.getProcessStateLocked(
- pkg, info.uid, versionCode, processName);
+ pkg, uid, versionCode, processName);
pkgList.put(pkg, holder);
if (holder.state != baseProcessTracker) {
holder.state.makeActive();
@@ -664,7 +666,7 @@
}
pkgList.clear();
ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
- info.packageName, info.uid, info.versionCode, processName);
+ info.packageName, uid, info.versionCode, processName);
ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
info.versionCode);
holder.state = ps;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5cde8ea..b4a44a6 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
@@ -83,14 +84,15 @@
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable) {
+ String instructionSet, boolean vmSafeMode, boolean debuggable,
+ @Nullable String outputPath) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode,
- debuggable);
+ debuggable, outputPath);
}
public int idmap(String targetApkPath, String overlayApkPath, int uid) {
@@ -134,6 +136,16 @@
return mInstaller.execute(builder.toString());
}
+ /**
+ * Removes packageDir or its subdirectory
+ */
+ public int rmPackageDir(String packageDir) {
+ StringBuilder builder = new StringBuilder("rmpackagedir");
+ builder.append(' ');
+ builder.append(packageDir);
+ return mInstaller.execute(builder.toString());
+ }
+
public int remove(String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
@@ -331,6 +343,15 @@
return (mInstaller.execute(builder.toString()) == 0);
}
+ public int createOatDir(String oatDir, String dexInstructionSet) {
+ StringBuilder builder = new StringBuilder("createoatdir");
+ builder.append(' ');
+ builder.append(oatDir);
+ builder.append(' ');
+ builder.append(dexInstructionSet);
+ return mInstaller.execute(builder.toString());
+ }
+
/**
* Returns true iff. {@code instructionSet} is a valid instruction set.
*/
diff --git a/services/core/java/com/android/server/pm/InstructionSets.java b/services/core/java/com/android/server/pm/InstructionSets.java
index 79e7a20..5092ebf 100644
--- a/services/core/java/com/android/server/pm/InstructionSets.java
+++ b/services/core/java/com/android/server/pm/InstructionSets.java
@@ -74,6 +74,7 @@
* a native bridge this might be different than the one shared libraries use.
*/
public static String getDexCodeInstructionSet(String sharedLibraryIsa) {
+ // TODO b/19550105 Build mapping once instead of querying each time
String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa);
return TextUtils.isEmpty(dexCodeIsa) ? sharedLibraryIsa : dexCodeIsa;
}
@@ -111,4 +112,13 @@
return allInstructionSets;
}
+
+ public static String getPrimaryInstructionSet(ApplicationInfo info) {
+ if (info.primaryCpuAbi == null) {
+ return getPreferredInstructionSet();
+ }
+
+ return VMRuntime.getInstructionSet(info.primaryCpuAbi);
+ }
+
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 2dbce0a..680ec4b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
import android.os.UserHandle;
@@ -23,6 +24,7 @@
import android.util.Log;
import android.util.Slog;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
@@ -38,7 +40,9 @@
* Helper class for running dexopt command on packages.
*/
final class PackageDexOptimizer {
- static final String TAG = "PackageManager.DexOptimizer";
+ private static final String TAG = "PackageManager.DexOptimizer";
+ static final String OAT_DIR_NAME = "oat";
+ // TODO b/19550105 Remove error codes and use exceptions
static final int DEX_OPT_SKIPPED = 0;
static final int DEX_OPT_PERFORMED = 1;
static final int DEX_OPT_DEFERRED = 2;
@@ -117,19 +121,30 @@
final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
pkg.packageName, dexCodeInstructionSet, defer);
if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
+ File oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
Log.i(TAG, "Running dexopt on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
- + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable);
+ + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ + " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
- vmSafeMode, debuggable);
- if (ret < 0) {
- // Don't bother running dexopt again if we failed, it will probably
- // just result in an error again. Also, don't bother dexopting for other
- // paths & ISAs.
- return DEX_OPT_FAILED;
+ if (oatDir != null) {
+ int ret = mPackageManagerService.mInstaller.dexopt(
+ path, sharedGid, !pkg.isForwardLocked(), pkg.packageName,
+ dexCodeInstructionSet, vmSafeMode, debuggable,
+ oatDir.getAbsolutePath());
+ if (ret < 0) {
+ return DEX_OPT_FAILED;
+ }
+ } else {
+ final int ret = mPackageManagerService.mInstaller
+ .dexopt(path, sharedGid,
+ !pkg.isForwardLocked(), pkg.packageName,
+ dexCodeInstructionSet,
+ vmSafeMode, debuggable, null);
+ if (ret < 0) {
+ return DEX_OPT_FAILED;
+ }
}
performedDexOpt = true;
@@ -186,6 +201,36 @@
return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
+ /**
+ * Creates oat dir for the specified package. In certain cases oat directory
+ * <strong>cannot</strong> be created:
+ * <ul>
+ * <li>{@code pkg} is a system app, which is not updated.</li>
+ * <li>Package location is not a directory, i.e. monolithic install.</li>
+ * </ul>
+ *
+ * @return oat directory or null, if oat directory cannot be created.
+ */
+ @Nullable
+ private File createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
+ throws IOException {
+ if (pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) {
+ return null;
+ }
+ File codePath = new File(pkg.codePath);
+ if (codePath.isDirectory()) {
+ File oatDir = getOatDir(codePath);
+ mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
+ dexInstructionSet);
+ return oatDir;
+ }
+ return null;
+ }
+
+ static File getOatDir(File codePath) {
+ return new File(codePath, OAT_DIR_NAME);
+ }
+
private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
boolean forceDex, boolean defer, ArraySet<String> done) {
for (String libName : libs) {
@@ -209,7 +254,7 @@
public void addPackageForDeferredDexopt(PackageParser.Package pkg) {
if (mDeferredDexOpt == null) {
- mDeferredDexOpt = new ArraySet<PackageParser.Package>();
+ mDeferredDexOpt = new ArraySet<>();
}
mDeferredDexOpt.add(pkg);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2150e9a..95d7a52 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -53,7 +53,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -225,9 +224,10 @@
for (File stage : unclaimedStages) {
Slog.w(TAG, "Deleting orphan stage " + stage);
if (stage.isDirectory()) {
- FileUtils.deleteContents(stage);
+ mPm.mInstaller.rmPackageDir(stage.getAbsolutePath());
+ } else {
+ stage.delete();
}
- stage.delete();
}
// Clean up orphaned icons
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aba930f..bb8a785 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -59,6 +59,7 @@
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
+import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import android.util.ArrayMap;
@@ -1872,9 +1873,10 @@
removeDataDirsLI(ps.name);
if (ps.codePath != null) {
if (ps.codePath.isDirectory()) {
- FileUtils.deleteContents(ps.codePath);
+ mInstaller.rmPackageDir(ps.codePath.getAbsolutePath());
+ } else {
+ ps.codePath.delete();
}
- ps.codePath.delete();
}
if (ps.resourcePath != null && !ps.resourcePath.equals(ps.codePath)) {
if (ps.resourcePath.isDirectory()) {
@@ -4190,9 +4192,10 @@
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
if (file.isDirectory()) {
- FileUtils.deleteContents(file);
+ mInstaller.rmPackageDir(file.getAbsolutePath());
+ } else {
+ file.delete();
}
- file.delete();
}
}
}
@@ -4640,7 +4643,7 @@
// Give priority to system apps.
for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
PackageParser.Package pkg = it.next();
- if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
+ if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding system app " + sortedPkgs.size() + ": " + pkg.packageName);
}
@@ -4651,7 +4654,7 @@
// Give priority to updated system apps.
for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
PackageParser.Package pkg = it.next();
- if (isUpdatedSystemApp(pkg)) {
+ if (pkg.isUpdatedSystemApp()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding updated system app " + sortedPkgs.size() + ": " + pkg.packageName);
}
@@ -4772,14 +4775,6 @@
return performDexOpt(packageName, instructionSet, false);
}
- private static String getPrimaryInstructionSet(ApplicationInfo info) {
- if (info.primaryCpuAbi == null) {
- return getPreferredInstructionSet();
- }
-
- return VMRuntime.getInstructionSet(info.primaryCpuAbi);
- }
-
public boolean performDexOpt(String packageName, String instructionSet, boolean backgroundDexopt) {
boolean dexopt = mLazyDexOpt || backgroundDexopt;
boolean updateUsage = !backgroundDexopt; // Don't update usage if this is just a backgroundDexopt
@@ -5513,7 +5508,7 @@
final String path = scanFile.getPath();
final String codePath = pkg.applicationInfo.getCodePath();
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
- if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
+ if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) {
setBundledAppAbisAndRoots(pkg, pkgSetting);
// If we haven't found any native libraries for the app, check if it has
@@ -5728,7 +5723,6 @@
throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI");
}
}
-
if (mFactoryTest && pkg.requestedPermissions.contains(
android.Manifest.permission.FACTORY_TEST)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
@@ -5744,7 +5738,7 @@
for (int i=0; i<pkg.libraryNames.size(); i++) {
String name = pkg.libraryNames.get(i);
boolean allowed = false;
- if (isUpdatedSystemApp(pkg)) {
+ if (pkg.isUpdatedSystemApp()) {
// New library entries can only be added through the
// system image. This is important to get rid of a lot
// of nasty edge cases: for example if we allowed a non-
@@ -6350,7 +6344,7 @@
final ApplicationInfo info = pkg.applicationInfo;
final String codePath = pkg.codePath;
final File codeFile = new File(codePath);
- final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info);
+ final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp();
final boolean asecApp = info.isForwardLocked() || isExternal(info);
info.nativeLibraryRootDir = null;
@@ -7002,7 +6996,7 @@
if (isSystemApp(pkg)) {
// For updated system applications, a system permission
// is granted only if it had been defined by the original application.
- if (isUpdatedSystemApp(pkg)) {
+ if (pkg.isUpdatedSystemApp()) {
final PackageSetting sysPs = mSettings
.getDisabledSystemPkgLPr(pkg.packageName);
final GrantedPermissions origGp = sysPs.sharedUser != null
@@ -7090,7 +7084,7 @@
}
public final void addActivity(PackageParser.Activity a, String type) {
- final boolean systemApp = isSystemApp(a.info.applicationInfo);
+ final boolean systemApp = a.info.applicationInfo.isSystemApp();
mActivities.put(a.getComponentName(), a);
if (DEBUG_SHOW_INFO)
Log.v(
@@ -7217,7 +7211,7 @@
} else {
res.icon = info.icon;
}
- res.system = isSystemApp(res.activityInfo.applicationInfo);
+ res.system = res.activityInfo.applicationInfo.isSystemApp();
return res;
}
@@ -7433,7 +7427,7 @@
res.labelRes = info.labelRes;
res.nonLocalizedLabel = info.nonLocalizedLabel;
res.icon = info.icon;
- res.system = isSystemApp(res.serviceInfo.applicationInfo);
+ res.system = res.serviceInfo.applicationInfo.isSystemApp();
return res;
}
@@ -7656,7 +7650,7 @@
res.labelRes = info.labelRes;
res.nonLocalizedLabel = info.nonLocalizedLabel;
res.icon = info.icon;
- res.system = isSystemApp(res.providerInfo.applicationInfo);
+ res.system = res.providerInfo.applicationInfo.isSystemApp();
return res;
}
@@ -9386,9 +9380,10 @@
}
if (codeFile.isDirectory()) {
- FileUtils.deleteContents(codeFile);
+ mInstaller.rmPackageDir(codeFile.getAbsolutePath());
+ } else {
+ codeFile.delete();
}
- codeFile.delete();
if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
resourceFile.delete();
@@ -9830,22 +9825,6 @@
return result;
}
- // Utility method used to ignore ADD/REMOVE events
- // by directory observer.
- private static boolean ignoreCodePath(String fullPathStr) {
- String apkName = deriveCodePathName(fullPathStr);
- int idx = apkName.lastIndexOf(INSTALL_PACKAGE_SUFFIX);
- if (idx != -1 && ((idx+1) < apkName.length())) {
- // Make sure the package ends with a numeral
- String version = apkName.substring(idx+1);
- try {
- Integer.parseInt(version);
- return true;
- } catch (NumberFormatException e) {}
- }
- return false;
- }
-
// Utility method that returns the relative package path with respect
// to the installation directory. Like say for /data/data/com.test-1.apk
// string com.test-1 is returned.
@@ -10454,17 +10433,27 @@
return;
}
+ // Run dexopt before old package gets removed, to minimize time when app is not available
+ int result = mPackageDexOptimizer
+ .performDexOpt(pkg, null /* instruction sets */, true /* forceDex */,
+ false /* defer */, false /* inclDependencies */);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
+ res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath);
+ return;
+ }
+
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
+ // Call with SCAN_NO_DEX, since dexopt has already been made
if (replace) {
- replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
+ replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING | SCAN_NO_DEX, args.user,
installerPackageName, res);
} else {
- installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
- args.user, installerPackageName, res);
+ installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES
+ | SCAN_NO_DEX, args.user, installerPackageName, res);
}
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(pkgName);
@@ -10502,10 +10491,6 @@
return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
- private static boolean isSystemApp(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
private static boolean isSystemApp(PackageSetting ps) {
return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
@@ -10514,14 +10499,6 @@
return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}
- private static boolean isUpdatedSystemApp(PackageParser.Package pkg) {
- return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
- private static boolean isUpdatedSystemApp(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
private int packageFlagsToInstallFlags(PackageSetting ps) {
int installFlags = 0;
if (isExternal(ps)) {
diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
deleted file mode 100644
index 2fe68f8..0000000
--- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
+++ /dev/null
@@ -1,33 +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.server.updates;
-
-import android.util.Base64;
-
-import java.io.IOException;
-
-public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver {
-
- public TZInfoInstallReceiver() {
- super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version");
- }
-
- @Override
- protected void install(byte[] encodedContent, int version) throws IOException {
- super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
- }
-}
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
new file mode 100644
index 0000000..b260e4e
--- /dev/null
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import libcore.tzdata.update.TzDataBundleInstaller;
+
+/**
+ * An install receiver responsible for installing timezone data updates.
+ */
+public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ private static final String TAG = "TZDataInstallReceiver";
+
+ private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+ private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
+ private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
+ private static final String UPDATE_VERSION_FILE_NAME = "version";
+ private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip";
+
+ private final TzDataBundleInstaller installer;
+
+ public TzDataInstallReceiver() {
+ super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
+ UPDATE_VERSION_FILE_NAME);
+ installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR);
+ }
+
+ @Override
+ protected void install(byte[] content, int version) throws IOException {
+ boolean valid = installer.install(content);
+ Slog.i(TAG, "Timezone data install valid for this device: " + valid);
+ // Even if !valid, we call super.install(). Only in the event of an exception should we
+ // not. If we didn't do this we could attempt to install repeatedly.
+ super.install(content, version);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 109785c..8998d39 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -504,7 +504,12 @@
int mLastDisplayFreezeDuration = 0;
Object mLastFinishedFreezeSource = null;
boolean mWaitingForConfig = false;
- boolean mWindowsFreezingScreen = false;
+
+ final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
+ final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
+ final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
+ private int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
+
boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -4626,19 +4631,19 @@
wtoken.hiddenRequested, HIDE_STACK_CRAWLS ?
null : new RuntimeException("here").fillInStackTrace());
+ mOpeningApps.remove(wtoken);
+ mClosingApps.remove(wtoken);
+ wtoken.waitingToShow = wtoken.waitingToHide = false;
+ wtoken.hiddenRequested = !visible;
+
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (okToDisplay() && mAppTransition.isTransitionSet()) {
- wtoken.hiddenRequested = !visible;
-
if (!wtoken.startingDisplayed) {
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Setting dummy animation on: " + wtoken);
wtoken.mAppAnimator.setDummyAnimation();
}
- mOpeningApps.remove(wtoken);
- mClosingApps.remove(wtoken);
- wtoken.waitingToShow = wtoken.waitingToHide = false;
wtoken.inPendingTransaction = true;
if (visible) {
mOpeningApps.add(wtoken);
@@ -4712,7 +4717,8 @@
WindowState w = wtoken.allAppWindows.get(i);
if (w.mAppFreezing) {
w.mAppFreezing = false;
- if (w.mHasSurface && !w.mOrientationChanging) {
+ if (w.mHasSurface && !w.mOrientationChanging
+ && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w);
w.mOrientationChanging = true;
mInnerFields.mOrientationChangeComplete = false;
@@ -4761,7 +4767,7 @@
if (mAppsFreezingScreen == 1) {
startFreezingDisplayLocked(false, 0, 0);
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000);
+ mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
}
}
final int N = wtoken.allAppWindows.size();
@@ -6550,7 +6556,7 @@
mAltOrientation = altOrientation;
mPolicy.setRotationLw(mRotation);
- mWindowsFreezingScreen = true;
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
@@ -7920,6 +7926,7 @@
// TODO(multidisplay): Can non-default displays rotate?
synchronized (mWindowMap) {
Slog.w(TAG, "Window freeze timeout expired.");
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
final WindowList windows = getDefaultWindowListLocked();
int i = windows.size();
while (i > 0) {
@@ -7991,6 +7998,7 @@
case APP_FREEZE_TIMEOUT: {
synchronized (mWindowMap) {
Slog.w(TAG, "App freeze timeout expired.");
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
final int numStacks = mStackIdToStack.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
@@ -9076,13 +9084,13 @@
// If the screen is currently frozen or off, then keep
// it frozen/off until this window draws at its new
// orientation.
- if (!okToDisplay()) {
+ if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "Changing surface while display frozen: " + w);
w.mOrientationChanging = true;
w.mLastFreezeDuration = 0;
mInnerFields.mOrientationChangeComplete = false;
- if (!mWindowsFreezingScreen) {
- mWindowsFreezingScreen = true;
+ if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// XXX should probably keep timeout from
// when we first froze the display.
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
@@ -10107,8 +10115,8 @@
"With display frozen, orientationChangeComplete="
+ mInnerFields.mOrientationChangeComplete);
if (mInnerFields.mOrientationChangeComplete) {
- if (mWindowsFreezingScreen) {
- mWindowsFreezingScreen = false;
+ if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
}
@@ -10394,7 +10402,7 @@
} else {
mInnerFields.mOrientationChangeComplete = true;
mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
- if (mWindowsFreezingScreen) {
+ if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
doRequest = true;
}
}
@@ -10739,7 +10747,8 @@
return;
}
- if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen
+ if (mWaitingForConfig || mAppsFreezingScreen > 0
+ || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
|| mClientFreezingScreen || !mOpeningApps.isEmpty()) {
if (DEBUG_ORIENTATION) Slog.d(TAG,
"stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 978f5c3..938c1ce 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1434,6 +1434,11 @@
mOrientationChanging = false;
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mService.mDisplayFreezeTime);
+ // We are assuming the hosting process is dead or in a zombie state.
+ Slog.w(TAG, "Failed to report 'resized' to the client of " + this
+ + ", removing this window.");
+ mService.mPendingRemove.add(this);
+ mService.requestTraversalLocked();
}
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index a6b862e..beb94fd 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -22,7 +22,7 @@
// STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary.
-#if HAVE_PRINTF_ZD
+#if !defined(_WIN32)
# define ZD "%zd"
# define ZD_TYPE ssize_t
# define STATUST(x) x
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 585a6de..c5fccbf 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -20,7 +20,7 @@
// SSIZE: mingw does not have signed size_t == ssize_t.
// STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary.
-#if HAVE_PRINTF_ZD
+#if !defined(_WIN32)
# define SSIZE(x) x
# define STATUST(x) x
#else
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index a18e9f1..9908c44 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -13,7 +13,7 @@
#include "ResourceTable.h"
// SSIZE: mingw does not have signed size_t == ssize_t.
-#if HAVE_PRINTF_ZD
+#if !defined(_WIN32)
# define ZD "%zd"
# define ZD_TYPE ssize_t
# define SSIZE(x) x
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index b38b2ed..9033cf7 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -18,7 +18,7 @@
// SSIZE: mingw does not have signed size_t == ssize_t.
// STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary.
-#if HAVE_PRINTF_ZD
+#if !defined(_WIN32)
# define SSIZE(x) x
# define STATUST(x) x
#else