diff options
72 files changed, 2469 insertions, 1675 deletions
diff --git a/api/current.txt b/api/current.txt index 392ab4d..cdc2404 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28506,6 +28506,53 @@ package android.security { public abstract class KeyStoreKeyProperties { } + public static abstract class KeyStoreKeyProperties.Algorithm { + field public static final java.lang.String AES = "AES"; + field public static final java.lang.String EC = "EC"; + field public static final java.lang.String HMAC_SHA1 = "HmacSHA1"; + field public static final java.lang.String HMAC_SHA224 = "HmacSHA224"; + field public static final java.lang.String HMAC_SHA256 = "HmacSHA256"; + field public static final java.lang.String HMAC_SHA384 = "HmacSHA384"; + field public static final java.lang.String HMAC_SHA512 = "HmacSHA512"; + field public static final java.lang.String RSA = "RSA"; + } + + public static abstract class KeyStoreKeyProperties.AlgorithmEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.BlockMode { + field public static final java.lang.String CBC = "CBC"; + field public static final java.lang.String CTR = "CTR"; + field public static final java.lang.String ECB = "ECB"; + field public static final java.lang.String GCM = "GCM"; + } + + public static abstract class KeyStoreKeyProperties.BlockModeEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.Digest { + field public static final java.lang.String MD5 = "MD5"; + field public static final java.lang.String NONE = "NONE"; + field public static final java.lang.String SHA1 = "SHA-1"; + field public static final java.lang.String SHA224 = "SHA-224"; + field public static final java.lang.String SHA256 = "SHA-256"; + field public static final java.lang.String SHA384 = "SHA-384"; + field public static final java.lang.String SHA512 = "SHA-512"; + } + + public static abstract class KeyStoreKeyProperties.DigestEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.EncryptionPadding { + field public static final java.lang.String NONE = "NoPadding"; + field public static final java.lang.String PKCS7 = "PKCS7Padding"; + field public static final java.lang.String RSA_OAEP = "OAEPPadding"; + field public static final java.lang.String RSA_PKCS1 = "PKCS1Padding"; + } + + public static abstract class KeyStoreKeyProperties.EncryptionPaddingEnum implements java.lang.annotation.Annotation { + } + public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 @@ -28525,6 +28572,14 @@ package android.security { public static abstract class KeyStoreKeyProperties.PurposeEnum implements java.lang.annotation.Annotation { } + public static abstract class KeyStoreKeyProperties.SignaturePadding { + field public static final java.lang.String RSA_PKCS1 = "PKCS1"; + field public static final java.lang.String RSA_PSS = "PSS"; + } + + public static abstract class KeyStoreKeyProperties.SignaturePaddingEnum implements java.lang.annotation.Annotation { + } + public class KeyStoreKeySpec implements java.security.spec.KeySpec { method public java.lang.String[] getBlockModes(); method public java.lang.String[] getDigests(); @@ -33951,14 +34006,6 @@ package android.util { } public deprecated class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); } public final class JsonReader implements java.io.Closeable { diff --git a/api/removed.txt b/api/removed.txt index 5e77e15..0046a70 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -108,6 +108,21 @@ package android.text.format { } +package android.util { + + public deprecated class FloatMath { + method public static float ceil(float); + method public static float cos(float); + method public static float exp(float); + method public static float floor(float); + method public static float hypot(float, float); + method public static float pow(float, float); + method public static float sin(float); + method public static float sqrt(float); + } + +} + package android.view { public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { diff --git a/api/system-current.txt b/api/system-current.txt index 029d611..7012773 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -30520,6 +30520,53 @@ package android.security { public abstract class KeyStoreKeyProperties { } + public static abstract class KeyStoreKeyProperties.Algorithm { + field public static final java.lang.String AES = "AES"; + field public static final java.lang.String EC = "EC"; + field public static final java.lang.String HMAC_SHA1 = "HmacSHA1"; + field public static final java.lang.String HMAC_SHA224 = "HmacSHA224"; + field public static final java.lang.String HMAC_SHA256 = "HmacSHA256"; + field public static final java.lang.String HMAC_SHA384 = "HmacSHA384"; + field public static final java.lang.String HMAC_SHA512 = "HmacSHA512"; + field public static final java.lang.String RSA = "RSA"; + } + + public static abstract class KeyStoreKeyProperties.AlgorithmEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.BlockMode { + field public static final java.lang.String CBC = "CBC"; + field public static final java.lang.String CTR = "CTR"; + field public static final java.lang.String ECB = "ECB"; + field public static final java.lang.String GCM = "GCM"; + } + + public static abstract class KeyStoreKeyProperties.BlockModeEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.Digest { + field public static final java.lang.String MD5 = "MD5"; + field public static final java.lang.String NONE = "NONE"; + field public static final java.lang.String SHA1 = "SHA-1"; + field public static final java.lang.String SHA224 = "SHA-224"; + field public static final java.lang.String SHA256 = "SHA-256"; + field public static final java.lang.String SHA384 = "SHA-384"; + field public static final java.lang.String SHA512 = "SHA-512"; + } + + public static abstract class KeyStoreKeyProperties.DigestEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.EncryptionPadding { + field public static final java.lang.String NONE = "NoPadding"; + field public static final java.lang.String PKCS7 = "PKCS7Padding"; + field public static final java.lang.String RSA_OAEP = "OAEPPadding"; + field public static final java.lang.String RSA_PKCS1 = "PKCS1Padding"; + } + + public static abstract class KeyStoreKeyProperties.EncryptionPaddingEnum implements java.lang.annotation.Annotation { + } + public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 @@ -30539,6 +30586,14 @@ package android.security { public static abstract class KeyStoreKeyProperties.PurposeEnum implements java.lang.annotation.Annotation { } + public static abstract class KeyStoreKeyProperties.SignaturePadding { + field public static final java.lang.String RSA_PKCS1 = "PKCS1"; + field public static final java.lang.String RSA_PSS = "PSS"; + } + + public static abstract class KeyStoreKeyProperties.SignaturePaddingEnum implements java.lang.annotation.Annotation { + } + public class KeyStoreKeySpec implements java.security.spec.KeySpec { method public java.lang.String[] getBlockModes(); method public java.lang.String[] getDigests(); @@ -36162,14 +36217,6 @@ package android.util { } public deprecated class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); } public final class JsonReader implements java.io.Closeable { diff --git a/api/system-removed.txt b/api/system-removed.txt index 5e77e15..0046a70 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -108,6 +108,21 @@ package android.text.format { } +package android.util { + + public deprecated class FloatMath { + method public static float ceil(float); + method public static float cos(float); + method public static float exp(float); + method public static float floor(float); + method public static float hypot(float, float); + method public static float pow(float, float); + method public static float sin(float); + method public static float sqrt(float); + } + +} + package android.view { public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index bb25ec6..21dc1e2 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -630,7 +630,10 @@ bool BootAnimation::movie() } glDisable(GL_SCISSOR_TEST); } - glDrawTexiOES(xc, yc, 0, animation.width, animation.height); + // specify the y center as ceiling((mHeight - animation.height) / 2) + // which is equivalent to mHeight - (yc + animation.height) + glDrawTexiOES(xc, mHeight - (yc + animation.height), + 0, animation.width, animation.height); eglSwapBuffers(mDisplay, mSurface); nsecs_t now = systemTime(); diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 5790682..cdd72be 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * This class enables automatic animations on layout changes in ViewGroup objects. To enable @@ -757,7 +758,7 @@ public class LayoutTransition { // reset the inter-animation delay, in case we use it later staggerDelay = 0; - final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup + final ViewTreeObserver observer = parent.getViewTreeObserver(); if (!observer.isAlive()) { // If the observer's not in a good state, skip the transition return; @@ -790,21 +791,9 @@ public class LayoutTransition { // This is the cleanup step. When we get this rendering event, we know that all of // the appropriate animations have been set up and run. Now we can clear out the // layout listeners. - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - parent.getViewTreeObserver().removeOnPreDrawListener(this); - int count = layoutChangeListenerMap.size(); - if (count > 0) { - Collection<View> views = layoutChangeListenerMap.keySet(); - for (View view : views) { - View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); - view.removeOnLayoutChangeListener(listener); - } - } - layoutChangeListenerMap.clear(); - return true; - } - }); + CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent); + observer.addOnPreDrawListener(callback); + parent.addOnAttachStateChangeListener(callback); } /** @@ -1499,4 +1488,50 @@ public class LayoutTransition { View view, int transitionType); } + /** + * Utility class to clean up listeners after animations are setup. Cleanup happens + * when either the OnPreDrawListener method is called or when the parent is detached, + * whichever comes first. + */ + private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener, + View.OnAttachStateChangeListener { + + final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap; + final ViewGroup parent; + + CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) { + this.layoutChangeListenerMap = listenerMap; + this.parent = parent; + } + + private void cleanup() { + parent.getViewTreeObserver().removeOnPreDrawListener(this); + parent.removeOnAttachStateChangeListener(this); + int count = layoutChangeListenerMap.size(); + if (count > 0) { + Collection<View> views = layoutChangeListenerMap.keySet(); + for (View view : views) { + View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); + view.removeOnLayoutChangeListener(listener); + } + layoutChangeListenerMap.clear(); + } + } + + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + cleanup(); + } + + @Override + public boolean onPreDraw() { + cleanup(); + return true; + } + }; + } diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 2939322..968c956 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -476,9 +476,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { tempRect.set(0, 0, width, height); view.getMatrix().mapRect(tempRect); - ViewGroup parent = (ViewGroup) view.getParent(); - left = leftInParent - tempRect.left + parent.getScrollX(); - top = topInParent - tempRect.top + parent.getScrollY(); + left = leftInParent - tempRect.left; + top = topInParent - tempRect.top; right = left + width; bottom = top + height; } @@ -506,7 +505,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { ViewGroup parent = (ViewGroup) view.getParent(); Matrix matrix = new Matrix(); parent.transformMatrixToLocal(matrix); - + matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); mSharedElementParentMatrices.add(matrix); } } @@ -521,6 +520,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { // Find the location in the view's parent ViewGroup parent = (ViewGroup) viewParent; parent.transformMatrixToLocal(matrix); + matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); } } else { // The indices of mSharedElementParentMatrices matches the diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index cf29310..3eeb04a 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1481,14 +1481,8 @@ public class TextToSpeech { // interface). // Sanitize locale using isLanguageAvailable. - int result = service.isLanguageAvailable( language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } + int result = service.isLanguageAvailable(language, country, variant); + if (result >= LANG_AVAILABLE) { // Get the default voice for the locale. String voiceName = service.getDefaultVoiceNameFor(language, country, variant); if (TextUtils.isEmpty(voiceName)) { @@ -1502,10 +1496,28 @@ public class TextToSpeech { return LANG_NOT_SUPPORTED; } + // Set the language/country/variant of the voice, so #getLanguage will return + // the currently set voice locale when called. + Voice voice = getVoice(service, voiceName); + String voiceLanguage = ""; + try { + voiceLanguage = voice.getLocale().getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + + voice.getLocale(), e); + } + + String voiceCountry = ""; + try { + voiceCountry = voice.getLocale().getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + + voice.getLocale(), e); + } mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName); - mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); - mParams.putString(Engine.KEY_PARAM_COUNTRY, country); - mParams.putString(Engine.KEY_PARAM_VARIANT, variant); + mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage); + mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry); + mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); } return result; } @@ -1654,20 +1666,32 @@ public class TextToSpeech { if (TextUtils.isEmpty(voiceName)) { return null; } - List<Voice> voices = service.getVoices(); - if (voices == null) { - return null; - } - for (Voice voice : voices) { - if (voice.getName().equals(voiceName)) { - return voice; - } - } - return null; + return getVoice(service, voiceName); } }, null, "getVoice"); } + + /** + * Returns a Voice instance of the voice with the given voice name. + * + * @return Voice instance with the given voice name, or {@code null} if not set or on error. + * + * @see Voice + */ + private Voice getVoice(ITextToSpeechService service, String voiceName) throws RemoteException { + List<Voice> voices = service.getVoices(); + if (voices == null) { + return null; + } + for (Voice voice : voices) { + if (voice.getName().equals(voiceName)) { + return voice; + } + } + return null; + } + /** * Returns a Voice instance that's the default voice for the default Text-to-speech language. * @return The default voice instance for the default language, or {@code null} if not set or @@ -1690,14 +1714,7 @@ public class TextToSpeech { // Sanitize the locale using isLanguageAvailable. int result = service.isLanguageAvailable(language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } - } else { + if (result < LANG_AVAILABLE) { // The default language is not supported. return null; } diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 8f488af..bb7d15f 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -25,6 +25,8 @@ package android.util; * {@link java.lang.Math}. {@link java.lang.Math} should be used in * preference. * + * <p>All methods were removed from the public API in version 23. + * * @deprecated Use {@link java.lang.Math} instead. */ @Deprecated @@ -39,6 +41,7 @@ public class FloatMath { * * @param value to be converted * @return the floor of value + * @removed */ public static float floor(float value) { return (float) Math.floor(value); @@ -50,6 +53,7 @@ public class FloatMath { * * @param value to be converted * @return the ceiling of value + * @removed */ public static float ceil(float value) { return (float) Math.ceil(value); @@ -60,6 +64,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the sine of angle + * @removed */ public static float sin(float angle) { return (float) Math.sin(angle); @@ -70,6 +75,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the cosine of angle + * @removed */ public static float cos(float angle) { return (float) Math.cos(angle); @@ -81,6 +87,7 @@ public class FloatMath { * * @param value to compute sqrt of * @return the square root of value + * @removed */ public static float sqrt(float value) { return (float) Math.sqrt(value); @@ -92,6 +99,7 @@ public class FloatMath { * * @param value to compute the exponential of * @return the exponential of value + * @removed */ public static float exp(float value) { return (float) Math.exp(value); @@ -104,6 +112,7 @@ public class FloatMath { * @param x the base of the operation. * @param y the exponent of the operation. * @return {@code x} to the power of {@code y}. + * @removed */ public static float pow(float x, float y) { return (float) Math.pow(x, y); @@ -116,6 +125,7 @@ public class FloatMath { * @param x a float number * @param y a float number * @return the hypotenuse + * @removed */ public static float hypot(float x, float y) { return (float) Math.hypot(x, y); diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index bc38e1a..9f46f45 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -40,8 +40,7 @@ public class GhostView extends View { mView.mGhostView = this; final ViewGroup parent = (ViewGroup) mView.getParent(); setGhostedVisibility(View.INVISIBLE); - parent.mRecreateDisplayList = true; - parent.updateDisplayListIfDirty(); + parent.invalidate(); } @Override diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 78604bf..040fd37 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -247,6 +247,13 @@ public final class InputMethodManager { /** @hide */ public static final int DISPATCH_HANDLED = 1; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_AUTO = 0; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES = 1; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2; + final IInputMethodManager mService; final Looper mMainLooper; @@ -1890,9 +1897,28 @@ public final class InputMethodManager { } } + /** + * Shows the input method chooser dialog. + * + * @param showAuxiliarySubtypes Set true to show auxiliary input methods. + * @hide + */ + public void showInputMethodPicker(boolean showAuxiliarySubtypes) { + synchronized (mH) { + try { + final int mode = showAuxiliarySubtypes ? + SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: + SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES; + mService.showInputMethodPickerFromClient(mClient, mode); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + private void showInputMethodPickerLocked() { try { - mService.showInputMethodPickerFromClient(mClient); + mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO); } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 52485dd..ce94727 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -196,7 +196,7 @@ public class InputMethodSubtypeSwitchingController { } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( - boolean showSubtypes, boolean inputShown, boolean isScreenLocked) { + boolean showSubtypes, boolean includeAuxiliarySubtypes, boolean isScreenLocked) { final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = @@ -205,6 +205,12 @@ public class InputMethodSubtypeSwitchingController { if (immis == null || immis.size() == 0) { return Collections.emptyList(); } + if (isScreenLocked && includeAuxiliarySubtypes) { + if (DEBUG) { + Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen."); + } + includeAuxiliarySubtypes = false; + } mSortedImmis.clear(); mSortedImmis.putAll(immis); for (InputMethodInfo imi : mSortedImmis.keySet()) { @@ -227,7 +233,7 @@ public class InputMethodSubtypeSwitchingController { final String subtypeHashCode = String.valueOf(subtype.hashCode()); // We show all enabled IMEs and subtypes when an IME is shown. if (enabledSubtypeSet.contains(subtypeHashCode) - && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { + && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) { final CharSequence subtypeLabel = subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(mContext, imi.getPackageName(), @@ -516,8 +522,8 @@ public class InputMethodSubtypeSwitchingController { } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, - boolean inputShown, boolean isScreenLocked) { + boolean includingAuxiliarySubtypes, boolean isScreenLocked) { return mSubtypeList.getSortedInputMethodAndSubtypeList( - showSubtypes, inputShown, isScreenLocked); + showSubtypes, includingAuxiliarySubtypes, isScreenLocked); } } diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java deleted file mode 100644 index 1a6736a..0000000 --- a/core/java/com/android/internal/transition/EpicenterClipReveal.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 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.internal.transition; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.RectEvaluator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.util.Property; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.PathInterpolator; - -import com.android.internal.R; - -/** - * EpicenterClipReveal captures the {@link View#getClipBounds()} before and - * after the scene change and animates between those and the epicenter bounds - * during a visibility transition. - */ -public class EpicenterClipReveal extends Visibility { - private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final boolean mCenterClipBounds; - - public EpicenterClipReveal() { - mInterpolatorX = null; - mInterpolatorY = null; - mCenterClipBounds = false; - } - - public EpicenterClipReveal(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterClipReveal, 0, 0); - - mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect clip = view.getClipBounds(); - values.values.put(PROPNAME_CLIP, clip); - - if (clip == null) { - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - } - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = getBestRect(endValues); - final Rect start = getEpicenterOrCenter(end); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = getBestRect(startValues); - final Rect end = getEpicenterOrCenter(start); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - // Translate the clip bounds to be centered within the target bounds. - if (mCenterClipBounds) { - final int offsetX = bestRect.centerX() - epicenter.centerX(); - final int offsetY = bestRect.centerY() - epicenter.centerY(); - epicenter.offset(offsetX, offsetY); - } - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private Rect getBestRect(TransitionValues values) { - final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); - if (clipRect == null) { - return (Rect) values.values.get(PROPNAME_BOUNDS); - } - return clipRect; - } - - private static Animator createRectAnimator(final View view, Rect start, Rect end, - TransitionValues endValues, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY) { - final RectEvaluator evaluator = new RectEvaluator(new Rect()); - final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); - - final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X); - final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y); - final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY); - animSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setClipBounds(terminalClip); - } - }); - return animSet; - } - - private static class ClipDimenProperty extends Property<View, Rect> { - public static final char TARGET_X = 'x'; - public static final char TARGET_Y = 'y'; - - private final Rect mTempRect = new Rect(); - - private final int mTargetDimension; - - public ClipDimenProperty(char targetDimension) { - super(Rect.class, "clip_bounds_" + targetDimension); - - mTargetDimension = targetDimension; - } - - @Override - public Rect get(View object) { - final Rect tempRect = mTempRect; - if (!object.getClipBounds(tempRect)) { - tempRect.setEmpty(); - } - return tempRect; - } - - @Override - public void set(View object, Rect value) { - final Rect tempRect = mTempRect; - if (object.getClipBounds(tempRect)) { - if (mTargetDimension == TARGET_X) { - tempRect.left = value.left; - tempRect.right = value.right; - } else { - tempRect.top = value.top; - tempRect.bottom = value.bottom; - } - object.setClipBounds(tempRect); - } - } - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslate.java b/core/java/com/android/internal/transition/EpicenterTranslate.java deleted file mode 100644 index eac3ff8..0000000 --- a/core/java/com/android/internal/transition/EpicenterTranslate.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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.internal.transition; - -import com.android.internal.R; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -/** - * EpicenterTranslate captures the {@link View#getTranslationX()} and - * {@link View#getTranslationY()} before and after the scene change and - * animates between those and the epicenter's center during a visibility - * transition. - */ -public class EpicenterTranslate extends Visibility { - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; - private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; - private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; - private static final String PROPNAME_Z = "android:epicenterReveal:z"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final TimeInterpolator mInterpolatorZ; - - public EpicenterTranslate() { - mInterpolatorX = null; - mInterpolatorY = null; - mInterpolatorZ = null; - } - - public EpicenterTranslate(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterTranslate, 0, 0); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorZ = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorZ, 0); - if (interpolatorZ != 0) { - mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); - } else { - mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); - values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); - values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); - values.values.put(PROPNAME_Z, view.getZ()); - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect start = getEpicenterOrCenter(end); - final float startX = start.centerX() - end.centerX(); - final float startY = start.centerY() - end.centerY(); - final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); - - // Translate the view to be centered on the epicenter. - view.setTranslationX(startX); - view.setTranslationY(startY); - view.setTranslationZ(startZ); - - final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect end = getEpicenterOrCenter(start); - final float endX = end.centerX() - start.centerX(); - final float endY = end.centerY() - start.centerY(); - final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); - - final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private static Animator createAnimator(final View view, float startX, float startY, - float startZ, float endX, float endY, float endZ, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ) { - final ObjectAnimator animX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ObjectAnimator animY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); - if (interpolatorZ != null) { - animZ.setInterpolator(interpolatorZ); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY, animZ); - return animSet; - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java new file mode 100644 index 0000000..2c10297 --- /dev/null +++ b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java @@ -0,0 +1,328 @@ +/* + * 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.internal.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.TypeEvaluator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.util.Property; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; + +import com.android.internal.R; + +/** + * EpicenterTranslateClipReveal captures the clip bounds and translation values + * before and after the scene change and animates between those and the + * epicenter bounds during a visibility transition. + */ +public class EpicenterTranslateClipReveal extends Visibility { + private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; + private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; + private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; + private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; + private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; + private static final String PROPNAME_Z = "android:epicenterReveal:z"; + + private final TimeInterpolator mInterpolatorX; + private final TimeInterpolator mInterpolatorY; + private final TimeInterpolator mInterpolatorZ; + + public EpicenterTranslateClipReveal() { + mInterpolatorX = null; + mInterpolatorY = null; + mInterpolatorZ = null; + } + + public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.EpicenterTranslateClipReveal, 0, 0); + + final int interpolatorX = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0); + if (interpolatorX != 0) { + mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); + } else { + mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; + } + + final int interpolatorY = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0); + if (interpolatorY != 0) { + mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); + } else { + mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; + } + + final int interpolatorZ = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0); + if (interpolatorZ != 0) { + mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); + } else { + mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; + } + + a.recycle(); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + captureValues(transitionValues); + } + + private void captureValues(TransitionValues values) { + final View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); + values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); + values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); + values.values.put(PROPNAME_Z, view.getZ()); + + final Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (endValues == null) { + return null; + } + + final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect startBounds = getEpicenterOrCenter(endBounds); + final float startX = startBounds.centerX() - endBounds.centerX(); + final float startY = startBounds.centerY() - endBounds.centerY(); + final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); + + // Translate the view to be centered on the epicenter. + view.setTranslationX(startX); + view.setTranslationY(startY); + view.setTranslationZ(startZ); + + final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect endClip = getBestRect(endValues); + final Rect startClip = getEpicenterOrCenter(endClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (startValues == null) { + return null; + } + + final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect endBounds = getEpicenterOrCenter(startBounds); + final float endX = endBounds.centerX() - startBounds.centerX(); + final float endY = endBounds.centerY() - startBounds.centerY(); + final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); + + final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect startClip = getBestRect(startValues); + final Rect endClip = getEpicenterOrCenter(startClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + private Rect getEpicenterOrCenter(Rect bestRect) { + final Rect epicenter = getEpicenter(); + if (epicenter != null) { + return epicenter; + } + + final int centerX = bestRect.centerX(); + final int centerY = bestRect.centerY(); + return new Rect(centerX, centerY, centerX, centerY); + } + + private Rect getBestRect(TransitionValues values) { + final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); + if (clipRect == null) { + return (Rect) values.values.get(PROPNAME_BOUNDS); + } + return clipRect; + } + + private static Animator createRectAnimator(final View view, State startX, State startY, + float startZ, State endX, State endY, float endZ, TransitionValues endValues, + TimeInterpolator interpolatorX, TimeInterpolator interpolatorY, + TimeInterpolator interpolatorZ) { + final StateEvaluator evaluator = new StateEvaluator(); + + final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); + if (interpolatorZ != null) { + animZ.setInterpolator(interpolatorZ); + } + + final StateProperty propX = new StateProperty(StateProperty.TARGET_X); + final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX); + if (interpolatorX != null) { + animX.setInterpolator(interpolatorX); + } + + final StateProperty propY = new StateProperty(StateProperty.TARGET_Y); + final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY); + if (interpolatorY != null) { + animY.setInterpolator(interpolatorY); + } + + final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); + final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setClipBounds(terminalClip); + } + }; + + final AnimatorSet animSet = new AnimatorSet(); + animSet.playTogether(animX, animY, animZ); + animSet.addListener(animatorListener); + return animSet; + } + + private static class State { + int lower; + int upper; + float trans; + + public State() {} + + public State(int lower, int upper, float trans) { + this.lower = lower; + this.upper = upper; + this.trans = trans; + } + } + + private static class StateEvaluator implements TypeEvaluator<State> { + private final State mTemp = new State(); + + @Override + public State evaluate(float fraction, State startValue, State endValue) { + mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction); + mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction); + mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction); + return mTemp; + } + } + + private static class StateProperty extends Property<View, State> { + public static final char TARGET_X = 'x'; + public static final char TARGET_Y = 'y'; + + private final Rect mTempRect = new Rect(); + private final State mTempState = new State(); + + private final int mTargetDimension; + + public StateProperty(char targetDimension) { + super(State.class, "state_" + targetDimension); + + mTargetDimension = targetDimension; + } + + @Override + public State get(View object) { + final Rect tempRect = mTempRect; + if (!object.getClipBounds(tempRect)) { + tempRect.setEmpty(); + } + final State tempState = mTempState; + if (mTargetDimension == TARGET_X) { + tempState.trans = object.getTranslationX(); + tempState.lower = tempRect.left + (int) tempState.trans; + tempState.upper = tempRect.right + (int) tempState.trans; + } else { + tempState.trans = object.getTranslationY(); + tempState.lower = tempRect.top + (int) tempState.trans; + tempState.upper = tempRect.bottom + (int) tempState.trans; + } + return tempState; + } + + @Override + public void set(View object, State value) { + final Rect tempRect = mTempRect; + if (object.getClipBounds(tempRect)) { + if (mTargetDimension == TARGET_X) { + tempRect.left = value.lower - (int) value.trans; + tempRect.right = value.upper - (int) value.trans; + } else { + tempRect.top = value.lower - (int) value.trans; + tempRect.bottom = value.upper - (int) value.trans; + } + object.setClipBounds(tempRect); + } + + if (mTargetDimension == TARGET_X) { + object.setTranslationX(value.trans); + } else { + object.setTranslationY(value.trans); + } + } + } +} diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java index 34f62ba..bd0e6ce 100644 --- a/core/java/com/android/internal/util/AsyncChannel.java +++ b/core/java/com/android/internal/util/AsyncChannel.java @@ -462,10 +462,8 @@ public class AsyncChannel { } catch(Exception e) { } // Tell source we're disconnected. - if (mSrcHandler != null) { - replyDisconnected(STATUS_SUCCESSFUL); - mSrcHandler = null; - } + replyDisconnected(STATUS_SUCCESSFUL); + mSrcHandler = null; // Unlink only when bindService isn't used if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) { mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0); @@ -871,6 +869,8 @@ public class AsyncChannel { * @param status to be stored in msg.arg1 */ private void replyDisconnected(int status) { + // Can't reply if already disconnected. Avoid NullPointerException. + if (mSrcHandler == null) return; Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED); msg.arg1 = status; msg.obj = this; diff --git a/core/java/com/android/internal/util/CallbackRegistry.java b/core/java/com/android/internal/util/CallbackRegistry.java new file mode 100644 index 0000000..0f228d4 --- /dev/null +++ b/core/java/com/android/internal/util/CallbackRegistry.java @@ -0,0 +1,395 @@ +/* + * 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.internal.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks callbacks for the event. This class supports reentrant modification + * of the callbacks during notification without adversely disrupting notifications. + * A common pattern for callbacks is to receive a notification and then remove + * themselves. This class handles this behavior with constant memory under + * most circumstances. + * + * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to + * the constructor to define how notifications should be called. That implementation + * does the actual notification on the listener.</p> + * + * <p>This class supports only callbacks with at most two parameters. + * Typically, these are the notification originator and a parameter, but these may + * be used as required. If more than two parameters are required or primitive types + * must be used, <code>A</code> should be some kind of containing structure that + * the subclass may reuse between notifications.</p> + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> Opaque argument used to pass additional data beyond an int. + */ +public class CallbackRegistry<C, T, A> implements Cloneable { + private static final String TAG = "CallbackRegistry"; + + /** An ordered collection of listeners waiting to be notified. */ + private List<C> mCallbacks = new ArrayList<C>(); + + /** + * A bit flag for the first 64 listeners that are removed during notification. + * The lowest significant bit corresponds to the 0th index into mCallbacks. + * For a small number of callbacks, no additional array of objects needs to + * be allocated. + */ + private long mFirst64Removed = 0x0; + + /** + * Bit flags for the remaining callbacks that are removed during notification. + * When there are more than 64 callbacks and one is marked for removal, a dynamic + * array of bits are allocated for the callbacks. + */ + private long[] mRemainderRemoved; + + /** + * The reentrancy level of the notification. When we notify a callback, it may cause + * further notifications. The reentrancy level must be tracked to let us clean up + * the callback state when all notifications have been processed. + */ + private int mNotificationLevel; + + /** The notification mechanism for notifying an event. */ + private final NotifierCallback<C, T, A> mNotifier; + + /** + * Creates an EventRegistry that notifies the event with notifier. + * @param notifier The class to use to notify events. + */ + public CallbackRegistry(NotifierCallback<C, T, A> notifier) { + mNotifier = notifier; + } + + /** + * Notify all callbacks. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + public synchronized void notifyCallbacks(T sender, int arg, A arg2) { + mNotificationLevel++; + notifyRecurseLocked(sender, arg, arg2); + mNotificationLevel--; + if (mNotificationLevel == 0) { + if (mRemainderRemoved != null) { + for (int i = mRemainderRemoved.length - 1; i >= 0; i--) { + final long removedBits = mRemainderRemoved[i]; + if (removedBits != 0) { + removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits); + mRemainderRemoved[i] = 0; + } + } + } + if (mFirst64Removed != 0) { + removeRemovedCallbacks(0, mFirst64Removed); + mFirst64Removed = 0; + } + } + } + + /** + * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyFirst64Locked(T sender, int arg, A arg2) { + final int maxNotified = Math.min(Long.SIZE, mCallbacks.size()); + notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed); + } + + /** + * Notify all callbacks using a recursive algorithm to avoid allocating on the heap. + * This part captures the callbacks beyond Long.SIZE that have no bits allocated for + * removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}. + * <p> + * Recursion is used to avoid allocating temporary state on the heap. Each stack has one + * long (64 callbacks) worth of information of which has been removed. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyRecurseLocked(T sender, int arg, A arg2) { + final int callbackCount = mCallbacks.size(); + final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1; + + // Now we've got all callbacks that have no mRemainderRemoved value, so notify the + // others. + notifyRemainderLocked(sender, arg, arg2, remainderIndex); + + // notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1 + // However, we must also keep track of those in mFirst64Removed, so we add 2 instead: + final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE; + + // The remaining have no bit set + notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0); + } + + /** + * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If + * remainderIndex is -1, the first 64 will be notified instead. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param remainderIndex The index into mRemainderRemoved that should be notified. + */ + private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) { + if (remainderIndex < 0) { + notifyFirst64Locked(sender, arg, arg2); + } else { + final long bits = mRemainderRemoved[remainderIndex]; + final int startIndex = (remainderIndex + 1) * Long.SIZE; + final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE); + notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1); + notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits); + } + } + + /** + * Notify callbacks from startIndex to endIndex, using bits as the bit status + * for whether they have been removed or not. bits should be from mRemainderRemoved or + * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to + * endIndex should be notified. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param startIndex The index into the mCallbacks to start notifying. + * @param endIndex One past the last index into mCallbacks to notify. + * @param bits A bit field indicating which callbacks have been removed and shouldn't + * be notified. + */ + private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex, + final int endIndex, final long bits) { + long bitMask = 1; + for (int i = startIndex; i < endIndex; i++) { + if ((bits & bitMask) == 0) { + mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); + } + bitMask <<= 1; + } + } + + /** + * Add a callback to be notified. If the callback is already in the list, another won't + * be added. This does not affect current notifications. + * @param callback The callback to add. + */ + public synchronized void add(C callback) { + int index = mCallbacks.lastIndexOf(callback); + if (index < 0 || isRemovedLocked(index)) { + mCallbacks.add(callback); + } + } + + /** + * Returns true if the callback at index has been marked for removal. + * + * @param index The index into mCallbacks to check. + * @return true if the callback at index has been marked for removal. + */ + private boolean isRemovedLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + return (mFirst64Removed & bitMask) != 0; + } else if (mRemainderRemoved == null) { + // It is after the first 64 callbacks, but nothing else was marked for removal. + return false; + } else { + final int maskIndex = (index / Long.SIZE) - 1; + if (maskIndex >= mRemainderRemoved.length) { + // There are some items in mRemainderRemoved, but nothing at the given index. + return false; + } else { + // There is something marked for removal, so we have to check the bit. + final long bits = mRemainderRemoved[maskIndex]; + final long bitMask = 1L << (index % Long.SIZE); + return (bits & bitMask) != 0; + } + } + } + + /** + * Removes callbacks from startIndex to startIndex + Long.SIZE, based + * on the bits set in removed. + * @param startIndex The index into the mCallbacks to start removing callbacks. + * @param removed The bits indicating removal, where each bit is set for one callback + * to be removed. + */ + private void removeRemovedCallbacks(int startIndex, long removed) { + // The naive approach should be fine. There may be a better bit-twiddling approach. + final int endIndex = startIndex + Long.SIZE; + + long bitMask = 1L << (Long.SIZE - 1); + for (int i = endIndex - 1; i >= startIndex; i--) { + if ((removed & bitMask) != 0) { + mCallbacks.remove(i); + } + bitMask >>>= 1; + } + } + + /** + * Remove a callback. This callback won't be notified after this call completes. + * @param callback The callback to remove. + */ + public synchronized void remove(C callback) { + if (mNotificationLevel == 0) { + mCallbacks.remove(callback); + } else { + int index = mCallbacks.lastIndexOf(callback); + if (index >= 0) { + setRemovalBitLocked(index); + } + } + } + + private void setRemovalBitLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + mFirst64Removed |= bitMask; + } else { + final int remainderIndex = (index / Long.SIZE) - 1; + if (mRemainderRemoved == null) { + mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE]; + } else if (mRemainderRemoved.length < remainderIndex) { + // need to make it bigger + long[] newRemainders = new long[mCallbacks.size() / Long.SIZE]; + System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length); + mRemainderRemoved = newRemainders; + } + final long bitMask = 1L << (index % Long.SIZE); + mRemainderRemoved[remainderIndex] |= bitMask; + } + } + + /** + * Makes a copy of the registered callbacks and returns it. + * + * @return a copy of the registered callbacks. + */ + public synchronized ArrayList<C> copyListeners() { + ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size()); + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + callbacks.add(mCallbacks.get(i)); + } + } + return callbacks; + } + + /** + * Returns true if there are no registered callbacks or false otherwise. + * + * @return true if there are no registered callbacks or false otherwise. + */ + public synchronized boolean isEmpty() { + if (mCallbacks.isEmpty()) { + return true; + } else if (mNotificationLevel == 0) { + return false; + } else { + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + return false; + } + } + return true; + } + } + + /** + * Removes all callbacks from the list. + */ + public synchronized void clear() { + if (mNotificationLevel == 0) { + mCallbacks.clear(); + } else if (!mCallbacks.isEmpty()) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + setRemovalBitLocked(i); + } + } + } + + public synchronized CallbackRegistry<C, T, A> clone() { + CallbackRegistry<C, T, A> clone = null; + try { + clone = (CallbackRegistry<C, T, A>) super.clone(); + clone.mFirst64Removed = 0; + clone.mRemainderRemoved = null; + clone.mNotificationLevel = 0; + clone.mCallbacks = new ArrayList<C>(); + final int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + clone.mCallbacks.add(mCallbacks.get(i)); + } + } + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return clone; + } + + /** + * Class used to notify events from CallbackRegistry. + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> An opaque argument to pass to the notifier + */ + public abstract static class NotifierCallback<C, T, A> { + /** + * Used to notify the callback. + * + * @param callback The callback to notify. + * @param sender The opaque sender object. + * @param arg The opaque notification parameter. + * @param arg2 An opaque argument passed in + * {@link CallbackRegistry#notifyCallbacks} + * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback) + */ + public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2); + } +} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 6f104dd..60c5e42 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -59,7 +59,8 @@ interface IInputMethodManager { int controlFlags, int softInputMode, int windowFlags, in EditorInfo attribute, IInputContext inputContext); - void showInputMethodPickerFromClient(in IInputMethodClient client); + void showInputMethodPickerFromClient(in IInputMethodClient client, + int auxiliarySubtypeMode); void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); void setInputMethod(in IBinder token, String id); void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype); diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 8ae2e3b..9babe82 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -188,18 +188,43 @@ size_t Bitmap::rowBytes() const { return mPixelRef->rowBytes(); } -SkPixelRef* Bitmap::pixelRef() const { +SkPixelRef* Bitmap::peekAtPixelRef() const { assertValid(); return mPixelRef.get(); } +SkPixelRef* Bitmap::refPixelRef() { + assertValid(); + android::AutoMutex _lock(mLock); + return refPixelRefLocked(); +} + +SkPixelRef* Bitmap::refPixelRefLocked() { + mPixelRef->ref(); + if (mPixelRef->unique()) { + // We just restored this from 0, pin the pixels and inc the strong count + // Note that there *might be* an incoming onStrongRefDestroyed from whatever + // last unref'd + pinPixelsLocked(); + mPinnedRefCount++; + } + return mPixelRef.get(); +} + void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { + { + android::AutoMutex _lock(mLock); + if (mPinnedRefCount) { + ALOGW("Called reconfigure on a bitmap that is in use! This may" + " cause graphical corruption!"); + } + } mPixelRef->reconfigure(info, rowBytes, ctable); } void Bitmap::reconfigure(const SkImageInfo& info) { - mPixelRef->reconfigure(info, mPixelRef->rowBytes(), mPixelRef->colorTable()); + reconfigure(info, mPixelRef->rowBytes(), mPixelRef->colorTable()); } void Bitmap::detachFromJava() { @@ -287,18 +312,10 @@ void Bitmap::unpinPixelsLocked() { void Bitmap::getSkBitmap(SkBitmap* outBitmap) { assertValid(); android::AutoMutex _lock(mLock); - mPixelRef->ref(); - if (mPixelRef->unique()) { - // We just restored this from 0, pin the pixels and inc the strong count - // Note that there *might be* an incoming onStrongRefDestroyed from whatever - // last unref'd - pinPixelsLocked(); - mPinnedRefCount++; - } // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes() // would require locking the pixels first. outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes()); - outBitmap->setPixelRef(mPixelRef.get())->unref(); + outBitmap->setPixelRef(refPixelRefLocked())->unref(); outBitmap->setHasHardwareMipMap(hasHardwareMipMap()); } @@ -323,7 +340,7 @@ public: } void* pixels() { - return mBitmap->pixelRef()->pixels(); + return mBitmap->peekAtPixelRef()->pixels(); } bool valid() { @@ -780,7 +797,7 @@ static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) { static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); - return static_cast<jint>(bitmap->pixelRef()->getGenerationID()); + return static_cast<jint>(bitmap->peekAtPixelRef()->getGenerationID()); } static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { @@ -800,10 +817,10 @@ static void Bitmap_setHasAlpha(JNIEnv* env, jobject, jlong bitmapHandle, jboolean hasAlpha, jboolean requestPremul) { LocalScopedBitmap bitmap(bitmapHandle); if (hasAlpha) { - bitmap->pixelRef()->changeAlphaType( + bitmap->peekAtPixelRef()->changeAlphaType( requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); } else { - bitmap->pixelRef()->changeAlphaType(kOpaque_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kOpaque_SkAlphaType); } } @@ -812,9 +829,9 @@ static void Bitmap_setPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle, LocalScopedBitmap bitmap(bitmapHandle); if (!bitmap->info().isOpaque()) { if (isPremul) { - bitmap->pixelRef()->changeAlphaType(kPremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kPremul_SkAlphaType); } else { - bitmap->pixelRef()->changeAlphaType(kUnpremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kUnpremul_SkAlphaType); } } } @@ -1164,7 +1181,7 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); - SkPixelRef* pixelRef = bitmap.valid() ? bitmap->pixelRef() : nullptr; + SkPixelRef* pixelRef = bitmap.valid() ? bitmap->peekAtPixelRef() : nullptr; SkSafeRef(pixelRef); return reinterpret_cast<jlong>(pixelRef); } diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h index d6e5c61..efeb898 100644 --- a/core/jni/android/graphics/Bitmap.h +++ b/core/jni/android/graphics/Bitmap.h @@ -62,7 +62,8 @@ public: int width() const { return info().width(); } int height() const { return info().height(); } size_t rowBytes() const; - SkPixelRef* pixelRef() const; + SkPixelRef* peekAtPixelRef() const; + SkPixelRef* refPixelRef(); bool valid() const { return mPixelStorageType != PixelStorageType::Invalid; } void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable); @@ -88,6 +89,7 @@ private: JNIEnv* jniEnv(); bool shouldDisposeSelfLocked(); void assertValid() const; + SkPixelRef* refPixelRefLocked(); android::Mutex mLock; int mPinnedRefCount = 0; diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index cdd397d..3ca4e72 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -184,7 +184,7 @@ public: } mBitmap->reconfigure(info, bitmap->rowBytes(), ctable); - bitmap->setPixelRef(mBitmap->pixelRef()); + bitmap->setPixelRef(mBitmap->refPixelRef())->unref(); // since we're already allocated, we lockPixels right away // HeapAllocator/JavaPixelAllocator behaves this way too @@ -258,7 +258,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding unsigned int existingBufferSize = 0; if (javaBitmap != NULL) { reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap); - if (reuseBitmap->pixelRef()->isImmutable()) { + if (reuseBitmap->peekAtPixelRef()->isImmutable()) { ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; reuseBitmap = nullptr; diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 0deb8cc..1c6f7de 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -352,8 +352,8 @@ void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap) getBitmap(env, bitmap)->getSkBitmap(outBitmap); } -SkPixelRef* GraphicsJNI::getSkPixelRef(JNIEnv* env, jobject bitmap) { - return getBitmap(env, bitmap)->pixelRef(); +SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) { + return getBitmap(env, bitmap)->refPixelRef(); } SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) { diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index e748bac..ef9c2a9 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -52,7 +52,7 @@ public: static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas); static android::Bitmap* getBitmap(JNIEnv*, jobject bitmap); static void getSkBitmap(JNIEnv*, jobject bitmap, SkBitmap* outBitmap); - static SkPixelRef* getSkPixelRef(JNIEnv*, jobject bitmap); + static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap); static SkRegion* getNativeRegion(JNIEnv*, jobject region); // Given the 'native' long held by the Rasterizer.java object, return a diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 1965cd3..77af341 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -180,7 +180,7 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot, screenshotInfo, rowBytes, nullptr); screenshot.detach(); - bitmap->pixelRef()->setImmutable(); + bitmap->peekAtPixelRef()->setImmutable(); return GraphicsJNI::createBitmap(env, bitmap, GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml index 89c3749..4b544d2 100644 --- a/core/res/res/layout-land/time_picker_material.xml +++ b/core/res/res/layout-land/time_picker_material.xml @@ -16,7 +16,6 @@ --> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -27,8 +26,7 @@ android:layout_column="0" android:layout_row="0" android:layout_rowSpan="3" - android:layout_gravity="center|fill" - tools:background="@color/accent_material_light" /> + android:layout_gravity="center|fill" /> <RelativeLayout android:layout_width="wrap_content" @@ -56,20 +54,14 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the right of the separator, regardless of the current locale's layout direction. --> @@ -80,10 +72,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> </LinearLayout> <!-- The layout alignment of this view will switch between toRightOf @@ -106,10 +95,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> <CheckedTextView android:id="@+id/pm_label" @@ -121,10 +107,7 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml index 2150341..a4388f6 100644 --- a/core/res/res/layout/date_picker_header_material.xml +++ b/core/res/res/layout/date_picker_header_material.xml @@ -16,17 +16,13 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/date_picker_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="18dp" android:paddingStart="?attr/dialogPreferredPadding" android:paddingEnd="?attr/dialogPreferredPadding" - android:orientation="vertical" - tools:background="@color/accent_material_light" - tools:paddingStart="24dp" - tools:paddingEnd="24dp"> + android:orientation="vertical"> <!-- Top padding should stay on this view so that the touch target is a bit larger. --> @@ -35,10 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" - android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" - tools:text="2015" - tools:textSize="@dimen/date_picker_year_label_size" - tools:textColor="@color/white" /> + android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" /> <TextView android:id="@+id/date_picker_header_date" @@ -47,9 +40,6 @@ android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel" android:gravity="start" android:maxLines="2" - android:ellipsize="none" - tools:text="Thu, Sep 30" - tools:textSize="@dimen/date_picker_date_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml index be9e443..3f5e300 100644 --- a/core/res/res/layout/time_picker_header_material.xml +++ b/core/res/res/layout/time_picker_header_material.xml @@ -18,13 +18,11 @@ <!-- This layout is duplicated in land/time_picker_material.xml, so any changes made here need to be manually copied over. --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/time_header" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" - android:padding="@dimen/timepicker_separator_padding" - tools:background="@color/accent_material_light"> + android:padding="@dimen/timepicker_separator_padding"> <!-- The hour should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -37,10 +35,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" @@ -50,10 +45,7 @@ android:layout_marginRight="@dimen/timepicker_separator_padding" android:layout_centerInParent="true" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -66,10 +58,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> <!-- The layout alignment of this view will switch between toRightOf @id/minutes and toLeftOf @id/hours depending on the locale. --> @@ -90,10 +79,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> <CheckedTextView android:id="@+id/pm_label" android:layout_width="wrap_content" @@ -103,9 +89,6 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/transition/popup_window_enter.xml b/core/res/res/transition/popup_window_enter.xml index 38c41f0..c4c8dac 100644 --- a/core/res/res/transition/popup_window_enter.xml +++ b/core/res/res/transition/popup_window_enter.xml @@ -16,17 +16,14 @@ <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together"> - <!-- Start from location of epicenter, move to popup location. --> - <transition - class="com.android.internal.transition.EpicenterTranslate" - android:duration="300" /> - <!-- Start from size of epicenter, expand to full width/height. --> <transition - class="com.android.internal.transition.EpicenterClipReveal" - android:centerClipBounds="true" - android:duration="300" /> + class="com.android.internal.transition.EpicenterTranslateClipReveal" + android:duration="250" /> <!-- Quickly fade in. --> - <fade android:duration="100" /> + <fade + android:duration="100" + android:fromAlpha="0.1" + android:toAlpha="1.0" /> </transitionSet> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 1c4b5f7..eaa6278 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5877,16 +5877,9 @@ </declare-styleable> <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterClipReveal"> - <attr name="centerClipBounds" format="boolean" /> + <declare-styleable name="EpicenterTranslateClipReveal"> <attr name="interpolatorX" format="reference" /> <attr name="interpolatorY" format="reference" /> - </declare-styleable> - - <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterTranslate"> - <attr name="interpolatorX" /> - <attr name="interpolatorY" /> <attr name="interpolatorZ" format="reference" /> </declare-styleable> diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java deleted file mode 100644 index f479e2b..0000000 --- a/core/tests/coretests/src/android/util/FloatMathTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007 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.util; - -import junit.framework.TestCase; -import android.test.suitebuilder.annotation.SmallTest; - -public class FloatMathTest extends TestCase { - - @SmallTest - public void testSqrt() { - assertEquals(7, FloatMath.sqrt(49), 0); - assertEquals(10, FloatMath.sqrt(100), 0); - assertEquals(0, FloatMath.sqrt(0), 0); - assertEquals(1, FloatMath.sqrt(1), 0); - } - - @SmallTest - public void testFloor() { - assertEquals(78, FloatMath.floor(78.89f), 0); - assertEquals(-79, FloatMath.floor(-78.89f), 0); - } - - @SmallTest - public void testCeil() { - assertEquals(79, FloatMath.ceil(78.89f), 0); - assertEquals(-78, FloatMath.ceil(-78.89f), 0); - } - - @SmallTest - public void testSin() { - assertEquals(0.0, FloatMath.sin(0), 0); - assertEquals(0.8414709848078965f, FloatMath.sin(1), 0); - } - - @SmallTest - public void testCos() { - assertEquals(1.0f, FloatMath.cos(0), 0); - assertEquals(0.5403023058681398f, FloatMath.cos(1), 0); - } -} diff --git a/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java new file mode 100644 index 0000000..c53f4cc --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java @@ -0,0 +1,305 @@ +/* + * 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.internal.util; + +import junit.framework.TestCase; + +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class CallbackRegistryTest extends TestCase { + + final Integer callback1 = 1; + final Integer callback2 = 2; + final Integer callback3 = 3; + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry; + int notify1; + int notify2; + int notify3; + int[] deepNotifyCount = new int[300]; + Integer argValue; + + private void addNotifyCount(Integer callback) { + if (callback == callback1) { + notify1++; + } else if (callback == callback2) { + notify2++; + } else if (callback == callback3) { + notify3++; + } + deepNotifyCount[callback]++; + } + + public void testAddListener() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertNotNull(registry.copyListeners()); + assertEquals(0, registry.copyListeners().size()); + + registry.add(callback); + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + Integer otherListener = 1; + registry.add(otherListener); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + assertEquals(otherListener, callbacks.get(1)); + + registry.remove(callback); + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(1)); + assertEquals(otherListener, callbacks.get(0)); + } + + public void testSimpleNotify() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + assertEquals(arg1, (int) arg); + addNotifyCount(callback); + argValue = arg; + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback2); + Integer arg = 1; + registry.notifyCallbacks(this, arg, arg); + assertEquals(arg, argValue); + assertEquals(1, notify2); + } + + public void testRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback1); + registry.remove(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback3, callbacks.get(0)); + } + + public void testDeepRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(2, notify2); + assertEquals(3, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(0, callbacks.size()); + } + + public void testAddRemovedListener() { + + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback2); + } else if (callback == callback3) { + registry.add(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(3, callbacks.size()); + assertEquals(callback1, callbacks.get(0)); + assertEquals(callback3, callbacks.get(1)); + assertEquals(callback2, callbacks.get(2)); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + } + + public void testVeryDeepRemoveWhileNotifying() { + final Integer[] callbacks = new Integer[deepNotifyCount.length]; + for (int i = 0; i < callbacks.length; i++) { + callbacks[i] = i; + } + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.remove(callbacks[callbacks.length - callback - 1]); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < callbacks.length; i++) { + registry.add(callbacks[i]); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + int expectedCount = Math.min(i + 1, deepNotifyCount.length - i); + assertEquals(expectedCount, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.clear(); + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(0, deepNotifyCount[i]); + } + } + + public void testNestedClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.clear(); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(1, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testIsEmpty() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertTrue(registry.isEmpty()); + registry.add(callback); + assertFalse(registry.isEmpty()); + } + + public void testClone() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + assertTrue(registry.isEmpty()); + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone(); + Integer callback = 0; + registry.add(callback); + assertFalse(registry.isEmpty()); + assertTrue(registry2.isEmpty()); + registry2 = registry.clone(); + assertFalse(registry2.isEmpty()); + } +} diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js index b28f978..8538671 100644 --- a/docs/html/jd_collections.js +++ b/docs/html/jd_collections.js @@ -1221,6 +1221,14 @@ var RESOURCE_COLLECTIONS = { "https://support.google.com/googleplay/answer/2651410" ] }, + "preview/landing/resources": { + "title": "", + "resources": [ + "preview/api-overview.html", + "preview/setup-sdk.html", + "preview/samples.html" + ] + }, "autolanding": { "title": "", "resources": [ diff --git a/docs/html/jd_tag_helpers.js b/docs/html/jd_tag_helpers.js index 7538e4d..f03b1d7 100644 --- a/docs/html/jd_tag_helpers.js +++ b/docs/html/jd_tag_helpers.js @@ -13,6 +13,7 @@ var ALL_RESOURCES = mergeArrays( GOOGLE_RESOURCES, GUIDE_RESOURCES, SAMPLES_RESOURCES, + PREVIEW_RESOURCES, TOOLS_RESOURCES, TRAINING_RESOURCES, YOUTUBE_RESOURCES, @@ -70,6 +71,7 @@ var ALL_RESOURCES_BY_TYPE = { 'google': GOOGLE_RESOURCES, 'guide': GUIDE_RESOURCES, 'samples': SAMPLES_RESOURCES, + 'preview': PREVIEW_RESOURCES, 'tools': TOOLS_RESOURCES, 'training': TRAINING_RESOURCES, 'youtube': YOUTUBE_RESOURCES, @@ -86,6 +88,7 @@ var ALL_RESOURCES_BY_TAG = mergeMaps( {map:GOOGLE_BY_TAG,arr:GOOGLE_RESOURCES}, {map:GUIDE_BY_TAG,arr:GUIDE_RESOURCES}, {map:SAMPLES_BY_TAG,arr:SAMPLES_RESOURCES}, + {map:PREVIEW_BY_TAG,arr:PREVIEW_RESOURCES}, {map:TOOLS_BY_TAG,arr:TOOLS_RESOURCES}, {map:TRAINING_BY_TAG,arr:TRAINING_RESOURCES}, {map:YOUTUBE_BY_TAG,arr:YOUTUBE_RESOURCES}, diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd index f72ffbb..dde3c7b 100644 --- a/docs/html/preview/api-overview.jd +++ b/docs/html/preview/api-overview.jd @@ -1,5 +1,5 @@ page.title=API Overview -excludeFromSuggestions=true +page.keywords=preview,sdk,compatibility sdk.platform.apiLevel=22 @jd:body diff --git a/docs/html/preview/index.jd b/docs/html/preview/index.jd new file mode 100644 index 0000000..a2d0b18 --- /dev/null +++ b/docs/html/preview/index.jd @@ -0,0 +1,60 @@ +page.title=M Developer Preview +page.tags=preview +meta.tags="preview" +fullpage=true +page.viewport_width=970 +section.landing=true +header.hide=1 +footer.hide=1 +@jd:body + +<section class="dac-expand dac-hero dac-light"> + <div class="wrap"> + <div class="cols dac-hero-content"> + <div class="col-1of2 col-push-1of2 dac-hero-figure"> + <img class="dac-hero-image" src="/design/media/hero-material-design.png"> + </div> + <div class="col-1of2 col-pull-1of2"> + <h1 class="dac-hero-title">M Developer Preview</h1> + <p class="dac-hero-description"> + Get ready for the next official release of the platform. Test your apps + and give us feedback! + </p> + <a class="dac-hero-cta" href="{@docRoot}preview/setup-sdk.html"> + <span class="dac-sprite dac-auto-chevron"></span> + Set up the Preview SDK + </a><br> + <a class="dac-hero-cta" href="{@docRoot}preview/api-overview.html"> + <span class="dac-sprite dac-auto-chevron"></span> + Review the API changes + </a><br> + <a class="dac-hero-cta" href="https://code.google.com/p/android-developer-preview/"> + <span class="dac-sprite dac-auto-chevron"></span> + Report issues + </a><br> + </div> + </div> + </div> +</section> + +<section class="dac-section dac-gray dac-small dac-invert"><div class="wrap"> + <h2 class="norule">Latest</h2> + <div class="resource-widget resource-flow-layout col-16" + data-query="collection:develop/landing/latest" + data-cardSizes="6x6" + data-maxResults="3"></div> +</div></section> + + +<section class="dac-section"><div class="wrap"> + <h1 class="dac-section-title">Resources</h1> + <div class="dac-section-subtitle"> + Check out these resources to help you get started with the M Developer Preview. + </div> + <div class="resource-widget resource-flow-layout col-16" + data-query="collection:preview/landing/resources" + data-cardSizes="6x6" + data-maxResults="6"></div> +</div></section> + + diff --git a/docs/html/preview/overview.jd b/docs/html/preview/overview.jd new file mode 100644 index 0000000..00f1cfe --- /dev/null +++ b/docs/html/preview/overview.jd @@ -0,0 +1,7 @@ +page.title=Preview Program Overview + +@jd:body + +<p> + This is an overview of the program. Bacon. +</p>
\ No newline at end of file diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs index bea4914..fbf73f6 100644 --- a/docs/html/preview/preview_toc.cs +++ b/docs/html/preview/preview_toc.cs @@ -1,6 +1,11 @@ <ul id="nav"> <li class="nav-section"> + <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/overview.html"> + Program Overview</a></div> + </li> + + <li class="nav-section"> <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/setup-sdk.html"> Set up the SDK</a></div> </li> @@ -29,9 +34,4 @@ License Agreement</a></div> </li> - <li class="nav-section" style="margin: 20px 0 0 -10px;"> - <div class="nav-section-header empty"><a href="<?cs var:toroot ?>index.html" class="back-link"> - Developer Home</a></div> - </li> - </ul> diff --git a/docs/html/preview/reference.jd b/docs/html/preview/reference.jd index ee1f24d..2d30c62 100644 --- a/docs/html/preview/reference.jd +++ b/docs/html/preview/reference.jd @@ -9,7 +9,7 @@ page.title=Reference <ul> <li> - <a href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip"> + <a href="http://storage.googleapis.com/androiddevelopers/preview/m-developer-preview-reference.zip"> M Developer Preview reference</a> </li> </ul>
\ No newline at end of file diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java index 39a5a53..e0a5345 100644 --- a/graphics/java/android/graphics/Atlas.java +++ b/graphics/java/android/graphics/Atlas.java @@ -21,10 +21,12 @@ package android.graphics; */ public class Atlas { /** - * This flag indicates whether the packing algorithm will attempt - * to rotate entries to make them fit better in the atlas. + * WARNING: These flag values are part of the on-disk configuration information, + * do not change their values. */ - public static final int FLAG_ALLOW_ROTATIONS = 0x1; + + /** DELETED: FLAG_ROTATION = 0x01 */ + /** * This flag indicates whether the packing algorithm should leave * an empty 1 pixel wide border around each bitmap. This border can @@ -52,9 +54,7 @@ public class Atlas { /** * Represents a bitmap packed in the atlas. Each entry has a location in - * pixels in the atlas and a rotation flag. If the entry was rotated, the - * bitmap must be rotated by 90 degrees (in either direction as long as - * the origin remains the same) before being rendered into the atlas. + * pixels in the atlas and a rotation flag. */ public static class Entry { /** @@ -65,11 +65,6 @@ public class Atlas { * Location, in pixels, of the bitmap on the Y axis in the atlas. */ public int y; - - /** - * If true, the bitmap must be rotated 90 degrees in the atlas. - */ - public boolean rotated; } private final Policy mPolicy; @@ -239,7 +234,6 @@ public class Atlas { private final SplitDecision mSplitDecision; - private final boolean mAllowRotation; private final int mPadding; /** @@ -263,7 +257,6 @@ public class Atlas { } SlicePolicy(int width, int height, int flags, SplitDecision splitDecision) { - mAllowRotation = (flags & FLAG_ALLOW_ROTATIONS) != 0; mPadding = (flags & FLAG_ADD_PADDING) != 0 ? 1 : 0; // The entire atlas is empty at first, minus padding @@ -360,26 +353,9 @@ public class Atlas { * * @return True if the rectangle was packed in the atlas, false otherwise */ - @SuppressWarnings("SuspiciousNameCombination") private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) { - boolean rotated = false; - - // If the rectangle doesn't fit we'll try to rotate it - // if possible before giving up if (cell.width < width || cell.height < height) { - if (mAllowRotation) { - if (cell.width < height || cell.height < width) { - return false; - } - - // Rotate the rectangle - int temp = width; - width = height; - height = temp; - rotated = true; - } else { - return false; - } + return false; } // Remaining free space after packing the rectangle @@ -433,7 +409,6 @@ public class Atlas { // Return the location and rotation of the packed rectangle entry.x = cell.x; entry.y = cell.y; - entry.rotated = rotated; return true; } diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java index 3b25ba6..3f29c6a 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -54,13 +54,13 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { public static class RSA extends AndroidKeyPairGenerator { public RSA() { - super("RSA"); + super(KeyStoreKeyProperties.Algorithm.RSA); } } public static class EC extends AndroidKeyPairGenerator { public EC() { - super("EC"); + super(KeyStoreKeyProperties.Algorithm.EC); } } @@ -83,15 +83,15 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { private android.security.KeyStore mKeyStore; private KeyPairGeneratorSpec mSpec; - private String mKeyAlgorithm; + private @KeyStoreKeyProperties.AlgorithmEnum String mKeyAlgorithm; private int mKeyType; private int mKeySize; - protected AndroidKeyPairGenerator(String algorithm) { + protected AndroidKeyPairGenerator(@KeyStoreKeyProperties.AlgorithmEnum String algorithm) { mAlgorithm = algorithm; } - public String getAlgorithm() { + public @KeyStoreKeyProperties.AlgorithmEnum String getAlgorithm() { return mAlgorithm; } @@ -197,7 +197,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { return certGen.generate(privateKey); } - private String getKeyAlgorithm(KeyPairGeneratorSpec spec) { + private @KeyStoreKeyProperties.AlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { String result = spec.getKeyType(); if (result != null) { return result; @@ -248,10 +248,11 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } } - private static String getDefaultSignatureAlgorithmForKeyAlgorithm(String algorithm) { - if ("RSA".equalsIgnoreCase(algorithm)) { + private static String getDefaultSignatureAlgorithmForKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(algorithm)) { return "sha256WithRSA"; - } else if ("EC".equalsIgnoreCase(algorithm)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(algorithm)) { return "sha256WithECDSA"; } else { throw new IllegalArgumentException("Unsupported key type " + algorithm); @@ -287,7 +288,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params; - String keyAlgorithm = getKeyAlgorithm(spec); + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithm = getKeyAlgorithm(spec); int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); if (keyType == -1) { throw new InvalidAlgorithmParameterException( diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index 72cb062..e82ff6a 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -128,10 +128,11 @@ public class AndroidKeyStore extends KeyStoreSpi { keymasterDigest = keymasterDigests.get(0); } - String keyAlgorithmString; + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithmString; try { - keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); + keyAlgorithmString = + KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Unsupported secret key type").initCause(e); @@ -451,10 +452,10 @@ public class AndroidKeyStore extends KeyStoreSpi { int keymasterAlgorithm; int keymasterDigest; try { - keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm( + keymasterAlgorithm = KeyStoreKeyProperties.Algorithm.toKeymasterSecretKeyAlgorithm( keyAlgorithmString); keymasterDigest = - KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString); + KeyStoreKeyProperties.Algorithm.toKeymasterDigest(keyAlgorithmString); } catch (IllegalArgumentException e) { throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); } @@ -465,8 +466,7 @@ public class AndroidKeyStore extends KeyStoreSpi { int[] keymasterDigests; if (params.isDigestsSpecified()) { // Digest(s) specified in parameters - keymasterDigests = - KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests()); + keymasterDigests = KeyStoreKeyProperties.Digest.allToKeymaster(params.getDigests()); if (keymasterDigest != -1) { // Digest also specified in the JCA key algorithm name. if (!com.android.internal.util.ArrayUtils.contains( @@ -494,8 +494,8 @@ public class AndroidKeyStore extends KeyStoreSpi { } @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - params.getBlockModes()); + int[] keymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { @@ -503,8 +503,7 @@ public class AndroidKeyStore extends KeyStoreSpi { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) + + KeyStoreKeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + ". See KeyStoreParameter documentation."); } } @@ -513,11 +512,11 @@ public class AndroidKeyStore extends KeyStoreSpi { args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); } args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - int[] keymasterPaddings = ArrayUtils.concat( - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - params.getEncryptionPaddings()), - KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings( - params.getSignaturePaddings())); + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + int[] keymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + params.getEncryptionPaddings()); args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); KeymasterUtils.addUserAuthArgs(args, params.getContext(), diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index e9c24dd..8e27dc3 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -262,7 +262,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, String host, int port, String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String alias) { choosePrivateKeyAlias(activity, response, keyTypes, issuers, host, port, null, alias); } @@ -306,9 +307,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, - String host, int port, String url, - String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String url, String alias) { /* * TODO currently keyTypes, issuers are unused. They are meant * to follow the semantics and purpose of X509KeyManager @@ -431,9 +431,11 @@ public final class KeyChain { * specific {@code PrivateKey} type indicated by {@code algorithm} (e.g., * "RSA"). */ - public static boolean isKeyAlgorithmSupported(String algorithm) { + public static boolean isKeyAlgorithmSupported( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { final String algUpper = algorithm.toUpperCase(Locale.US); - return "EC".equals(algUpper) || "RSA".equals(algUpper); + return KeyStoreKeyProperties.Algorithm.EC.equals(algUpper) + || KeyStoreKeyProperties.Algorithm.RSA.equals(algUpper); } /** @@ -443,7 +445,8 @@ public final class KeyChain { * hardware support that can be used to bind keys to the device in a way * that makes it non-exportable. */ - public static boolean isBoundKeyAlgorithm(String algorithm) { + public static boolean isBoundKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { if (!isKeyAlgorithmSupported(algorithm)) { return false; } diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java index 8f135a6..729646d 100644 --- a/keystore/java/android/security/KeyGeneratorSpec.java +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -48,8 +48,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -63,8 +63,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -160,14 +160,14 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -195,10 +195,12 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -220,8 +222,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -346,7 +348,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for keys which are used for encryption/decryption. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -357,7 +360,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for encryption/decryption keys. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -425,7 +428,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <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 + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index d6d3789..25c61fd 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -85,13 +85,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mDigests; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private final String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; @@ -138,10 +138,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] digests, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] blockModes, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -246,7 +246,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Returns the key type (e.g., "EC", "RSA") specified by this parameter. */ - public String getKeyType() { + public @KeyStoreKeyProperties.AlgorithmEnum String getKeyType() { return mKeyType; } @@ -352,28 +352,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding schemes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -403,14 +403,14 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Gets the duration of time (seconds) for which the private key can be used after the user - * is successfully authenticated. + * Gets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. * * <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. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ @@ -468,13 +468,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mDigests; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private String[] mSignaturePaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; @@ -511,7 +511,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the key type (e.g., EC, RSA) of the keypair to be created. */ - public Builder setKeyType(String keyType) throws NoSuchAlgorithmException { + public Builder setKeyType(@KeyStoreKeyProperties.AlgorithmEnum String keyType) + throws NoSuchAlgorithmException { if (keyType == null) { throw new NullPointerException("keyType == null"); } else { @@ -628,6 +629,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityEnd(Date) */ public Builder setKeyValidityStart(Date startDate) { @@ -640,6 +643,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityStart(Date) * @see #setKeyValidityForConsumptionEnd(Date) * @see #setKeyValidityForOriginationEnd(Date) @@ -655,6 +660,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityForConsumptionEnd(Date) */ public Builder setKeyValidityForOriginationEnd(Date endDate) { @@ -668,6 +675,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityForOriginationEnd(Date) */ public Builder setKeyValidityForConsumptionEnd(Date endDate) { @@ -679,6 +688,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Sets the set of purposes for which the key can be used. * * <p>This must be specified for all keys. There is no default. + * + * <p><b>NOTE: This has currently no effect. */ public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { mPurposes = purposes; @@ -690,8 +701,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * to use the key with any other digest will be rejected. * * <p>This must be specified for keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -702,8 +715,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * rejected. * * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -714,8 +730,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * rejected. * * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -725,8 +744,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Attempts to use the key with any other block modes will be rejected. * * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -750,6 +771,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <li>If you are using RSA encryption without padding, consider switching to padding * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> * </ul> + * + * <p><b>NOTE: This has currently no effect. */ public Builder setRandomizedEncryptionRequired(boolean required) { mRandomizedEncryptionRequired = required; @@ -772,6 +795,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * + * <p><b>NOTE: This has currently no effect. + * * @see #setUserAuthenticationValidityDurationSeconds(int) */ public Builder setUserAuthenticationRequired(boolean required) { @@ -788,7 +813,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <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 + * <p><b>NOTE: This has currently no effect. + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 82d328b..304d277 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -115,10 +115,10 @@ public class KeyStore { return mToken; } - static int getKeyTypeForAlgorithm(String keyType) { - if ("RSA".equalsIgnoreCase(keyType)) { + static int getKeyTypeForAlgorithm(@KeyStoreKeyProperties.AlgorithmEnum String keyType) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_RSA; - } else if ("EC".equalsIgnoreCase(keyType)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_EC; } else { return -1; diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java index 094aa75..bd601bc 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -27,6 +27,7 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; @@ -315,15 +316,15 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } else if (e instanceof InvalidAlgorithmParameterException) { throw (InvalidAlgorithmParameterException) e; } else { - throw new RuntimeException("Unexpected exception type", e); + throw new ProviderException("Unexpected exception type", e); } } if (mOperationToken == null) { - throw new IllegalStateException("Keystore returned null operation token"); + throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { - throw new IllegalStateException("Keystore returned invalid operation handle"); + throw new ProviderException("Keystore returned invalid operation handle"); } loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); @@ -494,13 +495,14 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } if ((mIv != null) && (mIv.length > 0)) { try { - AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + AlgorithmParameters params = + AlgorithmParameters.getInstance(KeyStoreKeyProperties.Algorithm.AES); params.init(new IvParameterSpec(mIv)); return params; } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e); + throw new ProviderException("Failed to obtain AES AlgorithmParameters", e); } catch (InvalidParameterSpecException e) { - throw new RuntimeException( + throw new ProviderException( "Failed to initialize AES AlgorithmParameters with an IV", e); } } @@ -633,10 +635,9 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if ((mIv == null) && (mEncrypting)) { // 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); - } + mAdditionalEntropyForBegin = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, mBlockSizeBytes); } } } @@ -668,11 +669,11 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if (mIv == null) { mIv = returnedIv; } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { - throw new IllegalStateException("IV in use differs from provided IV"); + throw new ProviderException("IV in use differs from provided IV"); } } else { if (returnedIv != null) { - throw new IllegalStateException( + throw new ProviderException( "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 index 1aa3aec..885f1f7 100644 --- a/keystore/java/android/security/KeyStoreConnectException.java +++ b/keystore/java/android/security/KeyStoreConnectException.java @@ -16,12 +16,14 @@ package android.security; +import java.security.ProviderException; + /** * Indicates a communications error with keystore service. * * @hide */ -public class KeyStoreConnectException extends IllegalStateException { +public class KeyStoreConnectException extends ProviderException { public KeyStoreConnectException() { super("Failed to communicate with keystore service"); } diff --git a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java index e5933ad..311278b 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterDefs; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.SecureRandom; /** * Assorted utility methods for implementing crypto operations on top of KeyStore. @@ -28,6 +29,9 @@ import java.security.InvalidKeyException; * @hide */ abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + private KeyStoreCryptoOperationUtils() {} /** @@ -81,4 +85,28 @@ abstract class KeyStoreCryptoOperationUtils { // General cases return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } } diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java index 0dbe788..5089a25 100644 --- a/keystore/java/android/security/KeyStoreHmacSpi.java +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -24,6 +24,7 @@ import android.security.keymaster.OperationResult; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; +import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.MacSpi; @@ -185,10 +186,10 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp } if (mOperationToken == null) { - throw new IllegalStateException("Keystore returned null operation token"); + throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { - throw new IllegalStateException("Keystore returned invalid operation handle"); + throw new ProviderException("Keystore returned invalid operation handle"); } mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( @@ -206,17 +207,17 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { - throw new IllegalStateException("Failed to reinitialize MAC", e); + throw new ProviderException("Failed to reinitialize MAC", e); } byte[] output; try { output = mChunkedStreamer.update(input, offset, len); } catch (KeyStoreException e) { - throw new IllegalStateException("Keystore operation failed", e); + throw new ProviderException("Keystore operation failed", e); } if ((output != null) && (output.length != 0)) { - throw new IllegalStateException("Update operation unexpectedly produced output"); + throw new ProviderException("Update operation unexpectedly produced output"); } } @@ -225,14 +226,14 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { - throw new IllegalStateException("Failed to reinitialize MAC", e); + throw new ProviderException("Failed to reinitialize MAC", e); } byte[] result; try { result = mChunkedStreamer.doFinal(null, 0, 0); } catch (KeyStoreException e) { - throw new IllegalStateException("Keystore operation failed", e); + throw new ProviderException("Keystore operation failed", e); } resetWhilePreservingInitState(); diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java index 68b5751..4b914c2 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import java.security.InvalidAlgorithmParameterException; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.util.Date; @@ -39,6 +40,17 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { public AES() { super(KeymasterDefs.KM_ALGORITHM_AES, 128); } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + super.engineInit(params, random); + if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { + throw new InvalidAlgorithmParameterException( + "Unsupported key size: " + mKeySizeBits + + ". Supported: 128, 192, 256."); + } + } } protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi { @@ -87,6 +99,11 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { private KeyGeneratorSpec mSpec; private SecureRandom mRng; + protected int mKeySizeBits; + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterPaddings; + protected KeyStoreKeyGeneratorSpi( int keymasterAlgorithm, int defaultKeySizeBits) { @@ -100,6 +117,97 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { mKeymasterAlgorithm = keymasterAlgorithm; mKeymasterDigest = keymasterDigest; mDefaultKeySizeBits = defaultKeySizeBits; + if (mDefaultKeySizeBits <= 0) { + throw new IllegalArgumentException("Default key size must be positive"); + } + + if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { + throw new IllegalArgumentException( + "Digest algorithm must be specified for HMAC key"); + } + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + 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"); + } + + mRng = random; + mSpec = spec; + + mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; + if (mKeySizeBits <= 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be positive: " + mKeySizeBits); + } else if ((mKeySizeBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + "Key size in must be a multiple of 8: " + mKeySizeBits); + } + + try { + mKeymasterPurposes = + KeyStoreKeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + mKeymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : mKeymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( + keymasterBlockMode)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by block mode: " + + KeyStoreKeyProperties.BlockMode.fromKeymaster( + keymasterBlockMode) + + ". See " + KeyGeneratorSpec.class.getName() + + " documentation."); + } + } + } + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mSpec = null; + mRng = null; + mKeySizeBits = -1; + mKeymasterPurposes = null; + mKeymasterPaddings = null; + mKeymasterBlockModes = null; } @Override @@ -117,43 +225,14 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); if (mKeymasterDigest != -1) { args.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); } - if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { - if (mKeymasterDigest == -1) { - throw new IllegalStateException("Digest algorithm must be specified for HMAC key"); - } - } - int keySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; - args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); - @KeyStoreKeyProperties.PurposeEnum int purposes = spec.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - spec.getBlockModes()); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) - && (spec.isRandomizedEncryptionRequired())) { - for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { - throw new IllegalStateException( - "Randomized encryption (IND-CPA) required but may be violated by block" - + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) - + ". See KeyGeneratorSpec documentation."); - } - } - } - - for (int keymasterPurpose : - KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) { - args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); - } - args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - args.addInts( - KeymasterDefs.KM_TAG_PADDING, - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - spec.getEncryptionPaddings())); + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); KeymasterUtils.addUserAuthArgs(args, spec.getContext(), spec.isUserAuthenticationRequired(), @@ -168,57 +247,31 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { (spec.getKeyValidityForConsumptionEnd() != null) ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (!spec.isRandomizedEncryptionRequired())) { // Permit caller-provided IV when encrypting with this key 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); - } - + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); int flags = spec.getFlags(); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); int errorCode = mKeyStore.generateKey( - keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics()); + keyAliasInKeystore, args, additionalEntropy, flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { - throw new IllegalStateException( + throw new ProviderException( "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); } - String keyAlgorithmJCA = - KeymasterUtils.getJcaSecretKeyAlgorithm(mKeymasterAlgorithm, mKeymasterDigest); - 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"); + String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); } - - mSpec = spec; - mRng = random; - } - - @Override - protected void engineInit(int keySize, SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without a " - + KeyGeneratorSpec.class.getName() + " parameter"); + return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); } } diff --git a/keystore/java/android/security/KeyStoreKeyProperties.java b/keystore/java/android/security/KeyStoreKeyProperties.java index b85ec53..1c3e300 100644 --- a/keystore/java/android/security/KeyStoreKeyProperties.java +++ b/keystore/java/android/security/KeyStoreKeyProperties.java @@ -17,13 +17,23 @@ package android.security; import android.annotation.IntDef; +import android.annotation.StringDef; import android.security.keymaster.KeymasterDefs; import libcore.util.EmptyArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; import java.util.Collection; +import java.util.Locale; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; /** * Properties of {@code AndroidKeyStore} keys. @@ -37,7 +47,7 @@ public abstract class KeyStoreKeyProperties { public @interface PurposeEnum {} /** - * Purpose of key. + * Purposes of key. */ public static abstract class Purpose { private Purpose() {} @@ -122,6 +132,514 @@ public abstract class KeyStoreKeyProperties { } @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Algorithm.RSA, + Algorithm.EC, + Algorithm.AES, + Algorithm.HMAC_SHA1, + Algorithm.HMAC_SHA224, + Algorithm.HMAC_SHA256, + Algorithm.HMAC_SHA384, + Algorithm.HMAC_SHA512, + }) + public @interface AlgorithmEnum {} + + /** + * Key algorithms. + * + * <p>These are standard names which can be used to obtain instances of {@link KeyGenerator}, + * {@link KeyPairGenerator}, {@link Cipher} (as part of the transformation string), {@link Mac}, + * {@link KeyFactory}, {@link SecretKeyFactory}. These are also the names used by + * {@link Key#getAlgorithm()}. + */ + public static abstract class Algorithm { + private Algorithm() {} + + /** Rivest Shamir Adleman (RSA) key. */ + public static final String RSA = "RSA"; + + /** Elliptic Curve (EC) key. */ + public static final String EC = "EC"; + + /** Advanced Encryption Standard (AES) key. */ + public static final String AES = "AES"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ + public static final String HMAC_SHA1 = "HmacSHA1"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ + public static final String HMAC_SHA224 = "HmacSHA224"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ + public static final String HMAC_SHA256 = "HmacSHA256"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ + public static final String HMAC_SHA384 = "HmacSHA384"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ + public static final String HMAC_SHA512 = "HmacSHA512"; + + /** + * @hide + */ + static int toKeymasterSecretKeyAlgorithm(@AlgorithmEnum String algorithm) { + if (AES.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_AES; + } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) { + return KeymasterDefs.KM_ALGORITHM_HMAC; + } else { + throw new IllegalArgumentException( + "Unsupported secret key algorithm: " + algorithm); + } + } + + /** + * @hide + */ + static @AlgorithmEnum String fromKeymasterSecretKeyAlgorithm( + int keymasterAlgorithm, int keymasterDigest) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_AES: + if (keymasterDigest != -1) { + throw new IllegalArgumentException("Digest not supported for AES key: " + + Digest.fromKeymaster(keymasterDigest)); + } + return AES; + case KeymasterDefs.KM_ALGORITHM_HMAC: + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_SHA1: + return HMAC_SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return HMAC_SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return HMAC_SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return HMAC_SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return HMAC_SHA512; + default: + throw new IllegalArgumentException("Unsupported HMAC digest: " + + Digest.fromKeymaster(keymasterDigest)); + } + default: + throw new IllegalArgumentException( + "Unsupported algorithm: " + keymasterAlgorithm); + } + } + + /** + * @hide + * + * @return keymaster digest or {@code -1} if the algorithm does not involve a digest. + */ + static int toKeymasterDigest(@AlgorithmEnum String algorithm) { + String algorithmUpper = algorithm.toUpperCase(Locale.US); + if (algorithmUpper.startsWith("HMAC")) { + String digestUpper = algorithmUpper.substring("HMAC".length()); + switch (digestUpper) { + 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( + "Unsupported HMAC digest: " + digestUpper); + } + } else { + return -1; + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + BlockMode.ECB, + BlockMode.CBC, + BlockMode.CTR, + BlockMode.GCM, + }) + public @interface BlockModeEnum {} + + /** + * Block modes that can be used when encrypting/decrypting using a key. + */ + public static abstract class BlockMode { + private BlockMode() {} + + /** Electronic Codebook (ECB) block mode. */ + public static final String ECB = "ECB"; + + /** Cipher Block Chaining (CBC) block mode. */ + public static final String CBC = "CBC"; + + /** Counter (CTR) block mode. */ + public static final String CTR = "CTR"; + + /** Galois/Counter Mode (GCM) block mode. */ + public static final String GCM = "GCM"; + + /** + * @hide + */ + static int toKeymaster(@BlockModeEnum String blockMode) { + if (ECB.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_ECB; + } else if (CBC.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CBC; + } else if (CTR.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CTR; + } else if (GCM.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_GCM; + } else { + throw new IllegalArgumentException("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String fromKeymaster(int blockMode) { + switch (blockMode) { + 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("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String[] allFromKeymaster(Collection<Integer> blockModes) { + if ((blockModes == null) || (blockModes.isEmpty())) { + return EmptyArray.STRING; + } + @BlockModeEnum String[] result = new String[blockModes.size()]; + int offset = 0; + for (int blockMode : blockModes) { + result[offset] = fromKeymaster(blockMode); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@BlockModeEnum String[] blockModes) { + if ((blockModes == null) || (blockModes.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[blockModes.length]; + for (int i = 0; i < blockModes.length; i++) { + result[i] = toKeymaster(blockModes[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + EncryptionPadding.NONE, + EncryptionPadding.PKCS7, + EncryptionPadding.RSA_PKCS1, + EncryptionPadding.RSA_OAEP, + }) + public @interface EncryptionPaddingEnum {} + + /** + * Padding schemes for encryption/decryption. + */ + public static abstract class EncryptionPadding { + private EncryptionPadding() {} + + /** + * No padding. + */ + public static final String NONE = "NoPadding"; + + /** + * PKCS#7 padding. + */ + public static final String PKCS7 = "PKCS7Padding"; + + /** + * RSA PKCS#1 v1.5 padding for encryption/decryption. + */ + public static final String RSA_PKCS1 = "PKCS1Padding"; + + /** + * RSA Optimal Asymmetric Encryption Padding (OAEP). + */ + public static final String RSA_OAEP = "OAEPPadding"; + + /** + * @hide + */ + static int toKeymaster(@EncryptionPaddingEnum String padding) { + if (NONE.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_NONE; + } else if (PKCS7.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_PKCS7; + } else if (RSA_PKCS1.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; + } else if (RSA_OAEP.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_OAEP; + } else { + throw new IllegalArgumentException( + "Unsupported encryption padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @EncryptionPaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_NONE: + return NONE; + case KeymasterDefs.KM_PAD_PKCS7: + return PKCS7; + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_OAEP: + return RSA_OAEP; + default: + throw new IllegalArgumentException( + "Unsupported encryption padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@EncryptionPaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + SignaturePadding.RSA_PKCS1, + SignaturePadding.RSA_PSS, + }) + public @interface SignaturePaddingEnum {} + + /** + * Padding schemes for signing/verification. + */ + public static abstract class SignaturePadding { + private SignaturePadding() {} + + /** + * RSA PKCS#1 v1.5 padding for signatures. + */ + public static final String RSA_PKCS1 = "PKCS1"; + + /** + * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. + */ + public static final String RSA_PSS = "PSS"; + + /** + * @hide + */ + static int toKeymaster(@SignaturePaddingEnum String padding) { + switch (padding.toUpperCase(Locale.US)) { + case RSA_PKCS1: + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; + case RSA_PSS: + return KeymasterDefs.KM_PAD_RSA_PSS; + default: + throw new IllegalArgumentException( + "Unsupported signature padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @SignaturePaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_PSS: + return RSA_PSS; + default: + throw new IllegalArgumentException("Unsupported signature padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@SignaturePaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Digest.NONE, + Digest.MD5, + Digest.SHA1, + Digest.SHA224, + Digest.SHA256, + Digest.SHA384, + Digest.SHA512, + }) + public @interface DigestEnum {} + + /** + * Digests that can be used with a key when signing or generating Message Authentication + * Codes (MACs). + */ + public static abstract class Digest { + private Digest() {} + + /** + * No digest: sign/authenticate the raw message. + */ + public static final String NONE = "NONE"; + + /** + * MD5 digest. + */ + public static final String MD5 = "MD5"; + + /** + * SHA-1 digest. + */ + public static final String SHA1 = "SHA-1"; + + /** + * SHA-2 224 (aka SHA-224) digest. + */ + public static final String SHA224 = "SHA-224"; + + /** + * SHA-2 256 (aka SHA-256) digest. + */ + public static final String SHA256 = "SHA-256"; + + /** + * SHA-2 384 (aka SHA-384) digest. + */ + public static final String SHA384 = "SHA-384"; + + /** + * SHA-2 512 (aka SHA-512) digest. + */ + public static final String SHA512 = "SHA-512"; + + /** + * @hide + */ + static int toKeymaster(@DigestEnum String digest) { + switch (digest.toUpperCase(Locale.US)) { + 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; + case NONE: + return KeymasterDefs.KM_DIGEST_NONE; + case MD5: + return KeymasterDefs.KM_DIGEST_MD5; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String fromKeymaster(int digest) { + 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("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String[] allFromKeymaster(Collection<Integer> digests) { + if (digests.isEmpty()) { + return EmptyArray.STRING; + } + String[] result = new String[digests.size()]; + int offset = 0; + for (int digest : digests) { + result[offset] = fromKeymaster(digest); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@DigestEnum String[] digests) { + if ((digests == null) || (digests.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[digests.length]; + int offset = 0; + for (@DigestEnum String digest : digests) { + result[offset] = toKeymaster(digest); + offset++; + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) @IntDef({Origin.GENERATED, Origin.IMPORTED, Origin.UNKNOWN}) public @interface OriginEnum {} diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java index 96d58d8..7533bdc 100644 --- a/keystore/java/android/security/KeyStoreKeySpec.java +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -32,10 +32,10 @@ public class KeyStoreKeySpec implements KeySpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserAuthenticationRequirementTeeEnforced; @@ -51,10 +51,10 @@ public class KeyStoreKeySpec implements KeySpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, boolean userAuthenticationRequirementTeeEnforced) { @@ -143,28 +143,28 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } /** * Gets the set of padding modes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding modes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } @@ -179,10 +179,10 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index b4747e9..8d7a19f 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -45,10 +45,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -60,10 +60,10 @@ public final class KeyStoreParameter implements ProtectionParameter { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -151,7 +151,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } @@ -159,7 +159,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * Gets the set of padding schemes with which the key can be used when signing or verifying * signatures. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } @@ -170,7 +170,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * @see #isDigestsSpecified() */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { if (mDigests == null) { throw new IllegalStateException("Digests not specified"); } @@ -190,7 +190,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -218,10 +218,12 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -251,10 +253,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mSignaturePaddings; - private String[] mDigests; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -292,6 +294,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityEnd(Date) */ public Builder setKeyValidityStart(Date startDate) { @@ -304,6 +308,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityStart(Date) * @see #setKeyValidityForConsumptionEnd(Date) * @see #setKeyValidityForOriginationEnd(Date) @@ -319,6 +325,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityForConsumptionEnd(Date) */ public Builder setKeyValidityForOriginationEnd(Date endDate) { @@ -332,6 +340,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityForOriginationEnd(Date) */ public Builder setKeyValidityForConsumptionEnd(Date endDate) { @@ -343,6 +353,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * Sets the set of purposes for which the key can be used. * * <p>This must be specified for all keys. There is no default. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { mPurposes = purposes; @@ -355,8 +367,11 @@ public final class KeyStoreParameter implements ProtectionParameter { * rejected. * * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -367,8 +382,11 @@ public final class KeyStoreParameter implements ProtectionParameter { * rejected. * * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -380,8 +398,10 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>For HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()}. For * asymmetric signing keys this constraint must be specified. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -391,8 +411,10 @@ public final class KeyStoreParameter implements ProtectionParameter { * Attempts to use the key with any other block modes will be rejected. * * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -430,6 +452,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * <li>If you are using RSA encryption without padding, consider switching to padding * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> * </ul> + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ public Builder setRandomizedEncryptionRequired(boolean required) { mRandomizedEncryptionRequired = required; @@ -449,6 +473,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More * information</a>. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setUserAuthenticationValidityDurationSeconds(int) */ public Builder setUserAuthenticationRequired(boolean required) { @@ -462,7 +488,9 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <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 + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java index bfe09e3..ff79b7a 100644 --- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -79,8 +79,8 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { int keySize; @KeyStoreKeyProperties.PurposeEnum int purposes; String[] encryptionPaddings; - String[] digests; - String[] blockModes; + @KeyStoreKeyProperties.DigestEnum String[] digests; + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; int keymasterHwEnforcedUserAuthenticators; try { @@ -105,10 +105,10 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { List<String> encryptionPaddingsList = new ArrayList<String>(); for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - String jcaPadding; + @KeyStoreKeyProperties.EncryptionPaddingEnum String jcaPadding; try { - jcaPadding = KeymasterUtils.getJcaEncryptionPaddingFromKeymasterPadding( - keymasterPadding); + jcaPadding = + KeyStoreKeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); } catch (IllegalArgumentException e) { throw new InvalidKeySpecException( "Unsupported encryption padding: " + keymasterPadding); @@ -118,9 +118,9 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { encryptionPaddings = encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - digests = KeymasterUtils.getJcaDigestAlgorithmsFromKeymasterDigests( + digests = KeyStoreKeyProperties.Digest.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeymasterUtils.getJcaBlockModesFromKeymasterBlockModes( + blockModes = KeyStoreKeyProperties.BlockMode.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); keymasterSwEnforcedUserAuthenticators = keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java index aa44ecd..df67ae7 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/KeymasterUtils.java @@ -21,11 +21,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; -import libcore.util.EmptyArray; - -import java.util.Collection; -import java.util.Locale; - /** * @hide */ @@ -33,152 +28,6 @@ public abstract class KeymasterUtils { private KeymasterUtils() {} - public static int getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - if ("AES".equalsIgnoreCase(jcaKeyAlgorithm)) { - return KeymasterDefs.KM_ALGORITHM_AES; - } else if (jcaKeyAlgorithm.toUpperCase(Locale.US).startsWith("HMAC")) { - return KeymasterDefs.KM_ALGORITHM_HMAC; - } else { - throw new IllegalArgumentException( - "Unsupported secret key algorithm: " + jcaKeyAlgorithm); - } - } - - public static String getJcaSecretKeyAlgorithm(int keymasterAlgorithm, int keymasterDigest) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_AES: - if (keymasterDigest != -1) { - throw new IllegalArgumentException( - "Digest not supported for AES key: " + keymasterDigest); - } - return "AES"; - case KeymasterDefs.KM_ALGORITHM_HMAC: - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_SHA1: - return "HmacSHA1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "HmacSHA224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "HmacSHA256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "HmacSHA384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "HmacSHA512"; - default: - throw new IllegalArgumentException( - "Unsupported HMAC digest: " + keymasterDigest); - } - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static String getJcaKeyPairAlgorithmFromKeymasterAlgorithm(int keymasterAlgorithm) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_RSA: - return "RSA"; - case KeymasterDefs.KM_ALGORITHM_EC: - return "EC"; - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static int getKeymasterDigestfromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - String algorithmUpper = jcaKeyAlgorithm.toUpperCase(Locale.US); - if (algorithmUpper.startsWith("HMAC")) { - String digestUpper = algorithmUpper.substring("HMAC".length()); - switch (digestUpper) { - 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("Unsupported HMAC digest: " + digestUpper); - } - } else { - return -1; - } - } - - public static int getKeymasterDigestFromJcaDigestAlgorithm(String jcaDigestAlgorithm) { - if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-1")) { - return KeymasterDefs.KM_DIGEST_SHA1; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-224")) { - return KeymasterDefs.KM_DIGEST_SHA_2_224; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-256")) { - return KeymasterDefs.KM_DIGEST_SHA_2_256; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-384")) { - return KeymasterDefs.KM_DIGEST_SHA_2_384; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-512")) { - return KeymasterDefs.KM_DIGEST_SHA_2_512; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("NONE")) { - return KeymasterDefs.KM_DIGEST_NONE; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("MD5")) { - return KeymasterDefs.KM_DIGEST_MD5; - } else { - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + jcaDigestAlgorithm); - } - } - - public static String getJcaDigestAlgorithmFromKeymasterDigest(int keymasterDigest) { - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_NONE: - return "NONE"; - case KeymasterDefs.KM_DIGEST_MD5: - return "MD5"; - case KeymasterDefs.KM_DIGEST_SHA1: - return "SHA-1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "SHA-224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "SHA-256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "SHA-384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + keymasterDigest); - } - } - - public static String[] getJcaDigestAlgorithmsFromKeymasterDigests( - Collection<Integer> keymasterDigests) { - if (keymasterDigests.isEmpty()) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterDigests.size()]; - int offset = 0; - for (int keymasterDigest : keymasterDigests) { - result[offset] = getJcaDigestAlgorithmFromKeymasterDigest(keymasterDigest); - offset++; - } - return result; - } - - public static int[] getKeymasterDigestsFromJcaDigestAlgorithms(String[] jcaDigestAlgorithms) { - if ((jcaDigestAlgorithms == null) || (jcaDigestAlgorithms.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaDigestAlgorithms.length]; - int offset = 0; - for (String jcaDigestAlgorithm : jcaDigestAlgorithms) { - result[offset] = getKeymasterDigestFromJcaDigestAlgorithm(jcaDigestAlgorithm); - offset++; - } - return result; - } - public static int getDigestOutputSizeBits(int keymasterDigest) { switch (keymasterDigest) { case KeymasterDefs.KM_DIGEST_NONE: @@ -200,60 +49,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterBlockModeFromJcaBlockMode(String jcaBlockMode) { - if ("ECB".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_ECB; - } else if ("CBC".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CBC; - } else if ("CTR".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CTR; - } else if ("GCM".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_GCM; - } else { - throw new IllegalArgumentException("Unsupported block mode: " + jcaBlockMode); - } - } - - public static String getJcaBlockModeFromKeymasterBlockMode(int keymasterBlockMode) { - switch (keymasterBlockMode) { - 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("Unsupported block mode: " + keymasterBlockMode); - } - } - - public static String[] getJcaBlockModesFromKeymasterBlockModes( - Collection<Integer> keymasterBlockModes) { - if ((keymasterBlockModes == null) || (keymasterBlockModes.isEmpty())) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterBlockModes.size()]; - int offset = 0; - for (int keymasterBlockMode : keymasterBlockModes) { - result[offset] = getJcaBlockModeFromKeymasterBlockMode(keymasterBlockMode); - offset++; - } - return result; - } - - public static int[] getKeymasterBlockModesFromJcaBlockModes(String[] jcaBlockModes) { - if ((jcaBlockModes == null) || (jcaBlockModes.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaBlockModes.length]; - for (int i = 0; i < jcaBlockModes.length; i++) { - result[i] = getKeymasterBlockModeFromJcaBlockMode(jcaBlockModes[i]); - } - return result; - } - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { switch (keymasterBlockMode) { case KeymasterDefs.KM_MODE_ECB: @@ -267,82 +62,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterPaddingFromJcaEncryptionPadding(String jcaPadding) { - if ("NoPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_NONE; - } else if ("PKCS7Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_PKCS7; - } else if ("PKCS1Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; - } else if ("OEAPPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_OAEP; - } else { - throw new IllegalArgumentException( - "Unsupported encryption padding scheme: " + jcaPadding); - } - } - - public static String getJcaEncryptionPaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_NONE: - return "NoPadding"; - case KeymasterDefs.KM_PAD_PKCS7: - return "PKCS7Padding"; - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: - return "PKCS1Padding"; - case KeymasterDefs.KM_PAD_RSA_OAEP: - return "OEAPPadding"; - default: - throw new IllegalArgumentException( - "Unsupported encryption padding: " + keymasterPadding); - } - } - - public static int getKeymasterPaddingFromJcaSignaturePadding(String jcaPadding) { - if ("PKCS#1".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; - } if ("PSS".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PSS; - } else { - throw new IllegalArgumentException( - "Unsupported signature padding scheme: " + jcaPadding); - } - } - - public static String getJcaSignaturePaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: - return "PKCS#1"; - case KeymasterDefs.KM_PAD_RSA_PSS: - return "PSS"; - default: - throw new IllegalArgumentException( - "Unsupported signature padding: " + keymasterPadding); - } - } - - public static int[] getKeymasterPaddingsFromJcaEncryptionPaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaEncryptionPadding(jcaPaddings[i]); - } - return result; - } - - public static int[] getKeymasterPaddingsFromJcaSignaturePaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaSignaturePadding(jcaPaddings[i]); - } - return result; - } - /** * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp index 882826e..2889d2f 100644 --- a/libs/hwui/AssetAtlas.cpp +++ b/libs/hwui/AssetAtlas.cpp @@ -112,9 +112,6 @@ private: Texture* const mDelegate; }; // struct DelegateTexture -/** - * TODO: This method does not take the rotation flag into account - */ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { const float width = float(mTexture->width); const float height = float(mTexture->height); @@ -128,7 +125,6 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { // pointers on 64 bit architectures. const int x = static_cast<int>(map[i++]); const int y = static_cast<int>(map[i++]); - bool rotated = map[i++] > 0; // Bitmaps should never be null, we're just extra paranoid if (!pixelRef) continue; @@ -142,7 +138,7 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { texture->width = pixelRef->info().width(); texture->height = pixelRef->info().height(); - Entry* entry = new Entry(pixelRef, x, y, rotated, texture, mapper, *this); + Entry* entry = new Entry(pixelRef, texture, mapper, *this); texture->uvMapper = &entry->uvMapper; mEntries.add(entry->pixelRef, entry); diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h index 17c5281..f1cd0b4 100644 --- a/libs/hwui/AssetAtlas.h +++ b/libs/hwui/AssetAtlas.h @@ -45,8 +45,8 @@ class Image; class AssetAtlas { public: /** - * Entry representing the position and rotation of a - * bitmap inside the atlas. + * Entry representing the texture and uvMapper of a PixelRef in the + * atlas */ class Entry { public: @@ -78,30 +78,15 @@ public: SkPixelRef* pixelRef; /** - * Location of the bitmap inside the atlas, in pixels. - */ - int x; - int y; - - /** - * If set, the bitmap is rotated 90 degrees (clockwise) - * inside the atlas. - */ - bool rotated; - - /** * Atlas this entry belongs to. */ const AssetAtlas& atlas; - Entry(SkPixelRef* pixelRef, int x, int y, bool rotated, - Texture* texture, const UvMapper& mapper, const AssetAtlas& atlas) + Entry(SkPixelRef* pixelRef, Texture* texture, const UvMapper& mapper, + const AssetAtlas& atlas) : texture(texture) , uvMapper(mapper) , pixelRef(pixelRef) - , x(x) - , y(y) - , rotated(rotated) , atlas(atlas) { } @@ -120,8 +105,7 @@ public: * Initializes the atlas with the specified buffer and * map. The buffer is a gralloc'd texture that will be * used as an EGLImage. The map is a list of SkBitmap* - * and their (x, y) positions as well as their rotation - * flags. + * and their (x, y) positions * * This method returns immediately if the atlas is already * initialized. To re-initialize the atlas, you must diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index 0521833..6d2de98 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -62,7 +62,7 @@ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } - SkPixelRef* pixelRef = GraphicsJNI::getSkPixelRef(env, jbitmap); + SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap); if (!pixelRef) { return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } @@ -71,9 +71,9 @@ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { void* addr = pixelRef->pixels(); if (NULL == addr) { pixelRef->unlockPixels(); + pixelRef->unref(); return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED; } - pixelRef->ref(); if (addrPtr) { *addrPtr = addr; @@ -86,7 +86,7 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } - SkPixelRef* pixelRef = GraphicsJNI::getSkPixelRef(env, jbitmap); + SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap); if (!pixelRef) { return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } @@ -98,6 +98,12 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { pixelRef->notifyPixelsChanged(); pixelRef->unlockPixels(); + // Awkward in that we need to double-unref as the call to get the SkPixelRef + // did a ref(), so we need to unref() for the local ref and for the previous + // AndroidBitmap_lockPixels(). However this keeps GraphicsJNI a bit safer + // if others start using it without knowing about android::Bitmap's "fun" + // ref counting mechanism(s). + pixelRef->unref(); pixelRef->unref(); return ANDROID_BITMAP_RESULT_SUCCESS; diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java index 845d53a..929258d 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java @@ -162,7 +162,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView switchImeButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mCallback.userActivity(); // Leave the screen on a bit longer - mImm.showInputMethodPicker(); + // Do not show auxiliary subtypes in password lock screen. + mImm.showInputMethodPicker(false /* showAuxiliarySubtypes */); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index c62ad66..7077a17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -144,7 +144,7 @@ public class NavigationBarView extends LinearLayout { @Override public void onClick(View view) { ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); + .showInputMethodPicker(true /* showAuxiliarySubtypes */); } }; diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java index 26f4232..ebc810f 100644 --- a/services/core/java/com/android/server/AssetAtlasService.java +++ b/services/core/java/com/android/server/AssetAtlasService.java @@ -119,7 +119,6 @@ public class AssetAtlasService extends IAssetAtlas.Stub { // long0: SkBitmap*, the native bitmap object // long1: x position // long2: y position - // long3: rotated, 1 if the bitmap must be rotated, 0 otherwise private long[] mAtlasMap; /** @@ -236,7 +235,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub { /** * Renders a list of bitmaps into the atlas. The position of each bitmap * was decided by the packing algorithm and will be honored by this - * method. If need be this method will also rotate bitmaps. + * method. * * @param buffer The buffer to render the atlas entries into * @param atlas The atlas to pack the bitmaps into @@ -280,16 +279,11 @@ public class AssetAtlasService extends IAssetAtlas.Stub { canvas.save(); canvas.translate(entry.x, entry.y); - if (entry.rotated) { - canvas.translate(bitmap.getHeight(), 0.0f); - canvas.rotate(90.0f); - } canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); canvas.restore(); atlasMap[mapIndex++] = bitmap.refSkPixelRef(); atlasMap[mapIndex++] = entry.x; atlasMap[mapIndex++] = entry.y; - atlasMap[mapIndex++] = entry.rotated ? 1 : 0; } } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 1019faa..32b91d2 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -512,7 +512,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private void onBluetoothGattServiceUp() { if (DBG) Log.d(TAG,"BluetoothGatt Service is Up"); try{ - if (isBleAppPresent() == false && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + if (isBleAppPresent() == false && mBluetooth != null + && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { mBluetooth.onLeServiceUp(); // waive WRITE_SECURE_SETTINGS permission check @@ -531,32 +532,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub { */ private void sendBrEdrDownCallback() { if (DBG) Log.d(TAG,"Calling sendBrEdrDownCallback callbacks"); - int n = mCallbacks.beginBroadcast(); + + if(mBluetooth == null) { + Log.w(TAG, "Bluetooth handle is null"); + return; + } if (isBleAppPresent() == false) { try { mBluetooth.onBrEdrDown(); } catch(RemoteException e) { - Log.e(TAG,"Unable to call onBrEdrDown", e); + Log.e(TAG, "Call to onBrEdrDown() failed.", e); } - } - else{//need to stay at BLE ON. disconnect all Gatt connections + } else { + // Need to stay at BLE ON. Disconnect all Gatt connections try{ - mBluetoothGatt.unregAll();//disconnectAll(); + mBluetoothGatt.unregAll(); } catch(RemoteException e) { - Log.e(TAG,"Unable to disconn all", e); - } - } - - Log.d(TAG,"Broadcasting onBrEdrDown() to " + n + " receivers."); - for (int i=0; i <n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBrEdrDown(); - } catch (RemoteException e) { - Log.e(TAG, "Unable to call sendBrEdrDownCallback() on callback #" + i, e); + Log.e(TAG, "Unable to disconnect all apps.", e); } } - mCallbacks.finishBroadcast(); } /** @hide*/ diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 638965a..9511f54 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -2236,7 +2236,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void showInputMethodPickerFromClient(IInputMethodClient client) { + public void showInputMethodPickerFromClient( + IInputMethodClient client, int auxiliarySubtypeMode) { if (!calledFromValidUser()) { return; } @@ -2249,7 +2250,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Always call subtype picker, because subtype picker is a superset of input method // picker. - mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); + mHandler.sendMessage(mCaller.obtainMessageI( + MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode)); } } @@ -2595,7 +2597,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub SomeArgs args; switch (msg.what) { case MSG_SHOW_IM_SUBTYPE_PICKER: - showInputMethodMenu(); + final boolean showAuxSubtypes; + switch (msg.arg1) { + case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO: + // This is undocumented so far, but IMM#showInputMethodPicker() has been + // implemented so that auxiliary subtypes will be excluded when the soft + // keyboard is invisible. + showAuxSubtypes = mInputShown; + break; + case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: + showAuxSubtypes = true; + break; + case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES: + showAuxSubtypes = false; + break; + default: + Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1); + return false; + } + showInputMethodMenu(showAuxSubtypes); return true; case MSG_SHOW_IM_SUBTYPE_ENABLER: @@ -2878,8 +2898,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); } - private void showInputMethodMenu() { - if (DEBUG) Slog.v(TAG, "Show switching menu"); + private void showInputMethodMenu(boolean showAuxSubtypes) { + if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes); final Context context = mContext; final boolean isScreenLocked = isScreenLocked(); @@ -2900,7 +2920,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final List<ImeSubtypeListItem> imList = mSwitchingController.getSortedInputMethodAndSubtypeListLocked( - true /* showSubtypes */, mInputShown, isScreenLocked); + true /* showSubtypes */, showAuxSubtypes, isScreenLocked); if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked(); diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index e434f39..8c12060 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -214,6 +214,10 @@ final class Constants { // values which denotes the device type in HDMI Spec 1.4. static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; + // TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event. + // True by default. + static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug"; + // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 89ffe45..fd3364a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -34,6 +34,9 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDevicePlayback"; + private static final boolean WAKE_ON_HOTPLUG = + SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true); + private boolean mIsActiveSource = false; // Used to keep the device awake while it is the active source. For devices that @@ -130,7 +133,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { assertRunOnServiceThread(); mCecMessageCache.flushAll(); // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. - if (connected && mService.isPowerStandbyOrTransient()) { + if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) { mService.wakeUp(); } if (!connected) { diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java index fd0c06d..d3df151 100644 --- a/telecomm/java/android/telecom/DefaultDialerManager.java +++ b/telecomm/java/android/telecom/DefaultDialerManager.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.provider.Settings; import android.text.TextUtils; @@ -151,14 +152,14 @@ public class DefaultDialerManager { for (ResolveInfo resolveInfo : resolveInfoList) { final ActivityInfo activityInfo = resolveInfo.activityInfo; - if (activityInfo == null) { - continue; + if (activityInfo != null && !packageNames.contains(activityInfo.packageName)) { + packageNames.add(activityInfo.packageName); } - packageNames.add(activityInfo.packageName); } - // TODO: Filter for apps that don't handle DIAL intent with tel scheme - return packageNames; + final Intent dialIntentWithTelScheme = new Intent(Intent.ACTION_DIAL); + dialIntentWithTelScheme.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, "", null)); + return filterByIntent(context, packageNames, dialIntentWithTelScheme); } /** @@ -182,6 +183,36 @@ public class DefaultDialerManager { || packageName.equals(tm.getSystemDialerPackage()); } + /** + * Filter a given list of package names for those packages that contain an activity that has + * an intent filter for a given intent. + * + * @param context A valid context + * @param packageNames List of package names to filter. + * @return The filtered list. + */ + private static List<String> filterByIntent(Context context, List<String> packageNames, + Intent intent) { + if (packageNames == null || packageNames.isEmpty()) { + return new ArrayList<>(); + } + + final List<String> result = new ArrayList<>(); + final List<ResolveInfo> resolveInfoList = + context.getPackageManager().queryIntentActivities(intent, 0); + final int length = resolveInfoList.size(); + for (int i = 0; i < length; i++) { + final ActivityInfo info = resolveInfoList.get(i).activityInfo; + if (info != null && packageNames.contains(info.packageName) + && !result.contains(info.packageName)) { + result.add(info.packageName); + } + } + + return result; + } + + private static TelecomManager getTelecomManager(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk index b7d7f98..9fc6403 100644 --- a/tests/OneMedia/Android.mk +++ b/tests/OneMedia/Android.mk @@ -9,9 +9,6 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) \ LOCAL_PACKAGE_NAME := OneMedia LOCAL_CERTIFICATE := platform -LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-media-protocols - LOCAL_JAVA_LIBRARIES += org.apache.http.legacy LOCAL_PROGUARD_ENABLED := disabled diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml index ef3fad5..c6824ec 100644 --- a/tests/OneMedia/AndroidManifest.xml +++ b/tests/OneMedia/AndroidManifest.xml @@ -27,15 +27,6 @@ android:name="com.android.onemedia.OnePlayerService" android:exported="true" android:process="com.android.onemedia.service" /> - <service - android:name=".provider.OneMediaRouteProvider" - android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE" - android:exported="true" - android:process="com.android.onemedia.provider"> - <intent-filter> - <action android:name="android.media.routing.MediaRouteService" /> - </intent-filter> - </service> </application> </manifest> diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 141a209..2455c9c 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -19,25 +19,17 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaMetadata; -import android.media.routing.MediaRouteSelector; -import android.media.routing.MediaRouter; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.RouteInfo; import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Bundle; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaStatus; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import com.android.onemedia.playback.LocalRenderer; -import com.android.onemedia.playback.OneMRPRenderer; import com.android.onemedia.playback.Renderer; import com.android.onemedia.playback.RequestUtils; @@ -48,7 +40,6 @@ public class PlayerSession { private static final String TAG = "PlayerSession"; protected MediaSession mSession; - protected MediaRouter mRouter; protected Context mContext; protected Renderer mRenderer; protected MediaSession.Callback mCallback; @@ -84,22 +75,11 @@ public class PlayerSession { .getSystemService(Context.MEDIA_SESSION_SERVICE); Log.d(TAG, "Creating session for package " + mContext.getBasePackageName()); - mRouter = new MediaRouter(mContext); - mRouter.addSelector(new MediaRouteSelector.Builder() - .addRequiredProtocol(MediaPlayerProtocol.class) - .build()); - mRouter.addSelector(new MediaRouteSelector.Builder() - .setRequiredFeatures(MediaRouter.ROUTE_FEATURE_LIVE_AUDIO) - .setOptionalFeatures(MediaRouter.ROUTE_FEATURE_LIVE_VIDEO) - .build()); - mRouter.setRoutingCallback(new RoutingCallback(), null); - mSession = new MediaSession(mContext, "OneMedia"); mSession.setCallback(mCallback); mSession.setPlaybackState(mPlaybackState); mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); - mSession.setMediaRouter(mRouter); mSession.setActive(true); updateMetadata(); } @@ -117,10 +97,6 @@ public class PlayerSession { mSession.release(); mSession = null; } - if (mRouter != null) { - mRouter.release(); - mRouter = null; - } } public void setListener(Listener listener) { @@ -278,63 +254,4 @@ public class PlayerSession { mRenderer.onPause(); } } - - private class RoutingCallback extends MediaRouter.RoutingCallback { - @Override - public void onConnectionStateChanged(int state) { - if (state == MediaRouter.CONNECTION_STATE_CONNECTING) { - if (mRenderer != null) { - mRenderer.onStop(); - } - mRenderer = null; - updateState(PlaybackState.STATE_CONNECTING); - return; - } - - MediaRouter.ConnectionInfo connection = mRouter.getConnection(); - if (connection != null) { - MediaPlayerProtocol protocol = - connection.getProtocolObject(MediaPlayerProtocol.class); - if (protocol != null) { - Log.d(TAG, "Connected to route using media player protocol"); - - protocol.setCallback(new PlayerCallback(), null); - mRenderer = new OneMRPRenderer(protocol); - updateState(PlaybackState.STATE_NONE); - return; - } - } - - // Use local route - mRenderer = new LocalRenderer(mContext, null); - mRenderer.registerListener(mRenderListener); - updateState(PlaybackState.STATE_NONE); - } - } - - private class PlayerCallback extends MediaPlayerProtocol.Callback { - @Override - public void onStatusUpdated(MediaStatus status, Bundle extras) { - if (status != null) { - Log.d(TAG, "Received status update: " + status.toBundle()); - switch (status.getPlayerState()) { - case MediaStatus.PLAYER_STATE_BUFFERING: - updateState(PlaybackState.STATE_BUFFERING); - break; - case MediaStatus.PLAYER_STATE_IDLE: - updateState(PlaybackState.STATE_STOPPED); - break; - case MediaStatus.PLAYER_STATE_PAUSED: - updateState(PlaybackState.STATE_PAUSED); - break; - case MediaStatus.PLAYER_STATE_PLAYING: - updateState(PlaybackState.STATE_PLAYING); - break; - case MediaStatus.PLAYER_STATE_UNKNOWN: - updateState(PlaybackState.STATE_NONE); - break; - } - } - } - } } diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java deleted file mode 100644 index 55eb92c..0000000 --- a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.android.onemedia.playback; - -import android.os.Bundle; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaInfo; - -/** - * Renderer for communicating with the OneMRP route - */ -public class OneMRPRenderer extends Renderer { - private final MediaPlayerProtocol mProtocol; - - public OneMRPRenderer(MediaPlayerProtocol protocol) { - super(null, null); - mProtocol = protocol; - } - - @Override - public void setContent(Bundle request) { - MediaInfo mediaInfo = new MediaInfo(request.getString(RequestUtils.EXTRA_KEY_SOURCE), - MediaInfo.STREAM_TYPE_BUFFERED, "audio/mp3"); - mProtocol.load(mediaInfo, true, 0, null); - } - - @Override - public boolean onStop() { - mProtocol.stop(null); - return true; - } - - @Override - public boolean onPlay() { - mProtocol.play(null); - return true; - } - - @Override - public boolean onPause() { - mProtocol.pause(null); - return true; - } - - @Override - public long getSeekPosition() { - return -1; - } -} diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java deleted file mode 100644 index 5845e48..0000000 --- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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.onemedia.provider; - -import android.media.routing.MediaRouteSelector; -import android.media.routing.MediaRouteService; -import android.media.routing.MediaRouter.ConnectionInfo; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.DiscoveryRequest; -import android.media.routing.MediaRouter.RouteInfo; -import android.media.session.PlaybackState; -import android.os.Bundle; -import android.os.Handler; -import android.os.Process; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaInfo; -import android.support.media.protocols.MediaPlayerProtocol.MediaStatus; -import android.os.Looper; -import android.os.ResultReceiver; -import android.os.SystemClock; -import android.util.Log; - -import com.android.onemedia.playback.LocalRenderer; -import com.android.onemedia.playback.Renderer; -import com.android.onemedia.playback.RequestUtils; - -import java.util.ArrayList; - -/** - * Test of MediaRouteProvider. Show a dummy provider with a simple interface for - * playing music. - */ -public class OneMediaRouteProvider extends MediaRouteService { - private static final String TAG = "OneMRP"; - private static final boolean DEBUG = true; - - private static final String TEST_DESTINATION_ID = "testDestination"; - private static final String TEST_ROUTE_ID = "testRoute"; - - private Renderer mRenderer; - private RenderListener mRenderListener; - private PlaybackState mPlaybackState; - private Handler mHandler; - - private OneStub mStub; - - @Override - public void onCreate() { - mHandler = new Handler(); - mRenderer = new LocalRenderer(this, null); - mRenderListener = new RenderListener(); - PlaybackState.Builder bob = new PlaybackState.Builder(); - bob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY); - mPlaybackState = bob.build(); - - mRenderer.registerListener(mRenderListener); - } - - @Override - public ClientSession onCreateClientSession(ClientInfo client) { - if (client.getUid() != Process.myUid()) { - // for testing purposes, only allow connections from this application - // since this provider is not fully featured - return null; - } - return new OneSession(client); - } - - private final class OneSession extends ClientSession { - private final ClientInfo mClient; - - public OneSession(ClientInfo client) { - mClient = client; - } - - @Override - public boolean onStartDiscovery(DiscoveryRequest req, DiscoveryCallback callback) { - for (MediaRouteSelector selector : req.getSelectors()) { - if (isMatch(selector)) { - DestinationInfo destination = new DestinationInfo.Builder( - TEST_DESTINATION_ID, getServiceMetadata(), "OneMedia") - .setDescription("Test route from OneMedia app.") - .build(); - ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>(); - routes.add(new RouteInfo.Builder( - TEST_ROUTE_ID, destination, selector).build()); - callback.onDestinationFound(destination, routes); - return true; - } - } - return false; - } - - @Override - public void onStopDiscovery() { - } - - @Override - public boolean onConnect(ConnectionRequest req, ConnectionCallback callback) { - if (req.getRoute().getId().equals(TEST_ROUTE_ID)) { - mStub = new OneStub(); - ConnectionInfo connection = new ConnectionInfo.Builder(req.getRoute()) - .setProtocolStub(MediaPlayerProtocol.class, mStub) - .build(); - callback.onConnected(connection); - return true; - } - return false; - } - - @Override - public void onDisconnect() { - mStub = null; - } - - private boolean isMatch(MediaRouteSelector selector) { - if (!selector.containsProtocol(MediaPlayerProtocol.class)) { - return false; - } - for (String protocol : selector.getRequiredProtocols()) { - if (!protocol.equals(MediaPlayerProtocol.class.getName())) { - return false; - } - } - return true; - } - } - - private final class OneStub extends MediaPlayerProtocol.Stub { - MediaInfo mMediaInfo; - - public OneStub() { - super(mHandler); - } - - @Override - public void onLoad(MediaInfo mediaInfo, boolean autoplay, long playPosition, - Bundle extras) { - if (DEBUG) { - Log.d(TAG, "Attempting to play " + mediaInfo.getContentId()); - } - // look up the route and send a play command to it - mMediaInfo = mediaInfo; - Bundle bundle = new Bundle(); - bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, mediaInfo.getContentId()); - mRenderer.setContent(bundle); - } - - @Override - public void onPlay(Bundle extras) { - mRenderer.onPlay(); - } - - @Override - public void onPause(Bundle extras) { - mRenderer.onPause(); - } - } - - private class RenderListener implements Renderer.Listener { - - @Override - public void onError(int type, int extra, Bundle extras, Throwable error) { - Log.d(TAG, "Sending onError with type " + type + " and extra " + extra); - sendStatusUpdate(PlaybackState.STATE_ERROR); - } - - @Override - public void onStateChanged(int newState) { - long position = -1; - if (mRenderer != null) { - position = mRenderer.getSeekPosition(); - } - int pbState; - float rate = 0; - String errorMsg = null; - switch (newState) { - case Renderer.STATE_ENDED: - case Renderer.STATE_STOPPED: - pbState = PlaybackState.STATE_STOPPED; - break; - case Renderer.STATE_INIT: - case Renderer.STATE_PREPARING: - pbState = PlaybackState.STATE_BUFFERING; - break; - case Renderer.STATE_ERROR: - pbState = PlaybackState.STATE_ERROR; - break; - case Renderer.STATE_PAUSED: - pbState = PlaybackState.STATE_PAUSED; - break; - case Renderer.STATE_PLAYING: - pbState = PlaybackState.STATE_PLAYING; - rate = 1; - break; - default: - pbState = PlaybackState.STATE_ERROR; - errorMsg = "unknown state"; - break; - } - PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState); - bob.setState(pbState, position, rate, SystemClock.elapsedRealtime()); - bob.setErrorMessage(errorMsg); - mPlaybackState = bob.build(); - - sendStatusUpdate(mPlaybackState.getState()); - } - - @Override - public void onBufferingUpdate(int percent) { - } - - @Override - public void onFocusLost() { - Log.d(TAG, "Focus lost, pausing"); - // Don't update state here, we'll get a separate call to - // onStateChanged when it pauses - mRenderer.onPause(); - } - - @Override - public void onNextStarted() { - } - - private void sendStatusUpdate(int state) { - if (mStub != null) { - MediaStatus status = new MediaStatus(1, mStub.mMediaInfo); - switch (state) { - case PlaybackState.STATE_BUFFERING: - case PlaybackState.STATE_FAST_FORWARDING: - case PlaybackState.STATE_REWINDING: - case PlaybackState.STATE_SKIPPING_TO_NEXT: - case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: - status.setPlayerState(MediaStatus.PLAYER_STATE_BUFFERING); - break; - case PlaybackState.STATE_CONNECTING: - case PlaybackState.STATE_STOPPED: - status.setPlayerState(MediaStatus.PLAYER_STATE_IDLE); - break; - case PlaybackState.STATE_PAUSED: - status.setPlayerState(MediaStatus.PLAYER_STATE_PAUSED); - break; - case PlaybackState.STATE_PLAYING: - status.setPlayerState(MediaStatus.PLAYER_STATE_PLAYING); - break; - case PlaybackState.STATE_NONE: - case PlaybackState.STATE_ERROR: - default: - status.setPlayerState(MediaStatus.PLAYER_STATE_UNKNOWN); - break; - } - mStub.sendStatusUpdatedEvent(status, null); - } - } - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index c44a57c..8899e53 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -164,7 +164,8 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public void showInputMethodPickerFromClient(IInputMethodClient arg0) throws RemoteException { + public void showInputMethodPickerFromClient(IInputMethodClient arg0, + int arg1) throws RemoteException { // TODO Auto-generated method stub } |
