diff options
Diffstat (limited to 'core/java')
71 files changed, 2398 insertions, 556 deletions
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index da48709..02a329d 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -16,7 +16,12 @@ package android.animation; +import android.content.res.Configuration; import android.content.res.ConstantState; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.animation.AnimationUtils; import java.util.ArrayList; @@ -25,6 +30,29 @@ import java.util.ArrayList; * started, ended, and have <code>AnimatorListeners</code> added to them. */ public abstract class Animator implements Cloneable { + /** + * Set this hint when duration for the animation does not need to be scaled. By default, no + * scaling is applied to the duration. + */ + public static final int HINT_NO_SCALE = 0; + + /** + * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)} when the animation's + * moving distance is proportional to the screen size. (e.g. a view coming in from the bottom of + * the screen to top/center). With this scale hint set, the animation duration will be + * automatically scaled based on screen size. + */ + public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1; + + /** + * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)}) if the animation + * has pre-defined moving distance in dp that does not vary from device to device. This is + * extremely useful when the animation needs to run on both phones/tablets and TV, because TV + * has inflated dp and therefore will have a longer visual arc for the same animation than on + * the phone. This hint is used to calculate a scaling factor to compensate for different + * visual arcs while maintaining the same angular velocity for the animation. + */ + public static final int HINT_DISTANCE_DEFINED_IN_DP = 2; /** * The set of listeners to be sent events through the life of an animation. @@ -55,6 +83,24 @@ public abstract class Animator implements Cloneable { private AnimatorConstantState mConstantState; /** + * Scaling factor for an animation that moves across the whole screen. + */ + float mScreenSizeBasedDurationScale = 1.0f; + + /** + * Scaling factor for an animation that is defined to move the same amount of dp across all + * devices. + */ + float mDpBasedDurationScale = 1.0f; + + /** + * By default, the scaling assumes the animation moves across the entire screen. + */ + int mDurationScaleHint = HINT_NO_SCALE; + + private final static boolean ANIM_DEBUG = false; + + /** * Starts this animation. If the animation has a nonzero startDelay, the animation will start * running after that delay elapses. A non-delayed animation will have its initial * value(s) set immediately, followed by calls to @@ -184,6 +230,78 @@ public abstract class Animator implements Cloneable { public abstract long getDuration(); /** + * Hints how duration scaling factor should be calculated. The duration will not be scaled when + * hint is set to {@link #HINT_NO_SCALE}. Otherwise, the duration will be automatically scaled + * per device to achieve the same look and feel across different devices. In order to do + * that, the same angular velocity of the animation will be needed on different devices in + * users' field of view. Therefore, the duration scale factor is determined by the ratio of the + * angular movement on current devices to that on the baseline device (i.e. Nexus 5). + * + * @param hint an indicator on how the animation is defined. The hint could be + * {@link #HINT_NO_SCALE}, {@link #HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE} or + * {@link #HINT_DISTANCE_DEFINED_IN_DP}. + * @param res The resources {@see android.content.res.Resources} for getting display metrics + */ + public void setDurationScaleHint(int hint, Resources res) { + if (ANIM_DEBUG) { + Log.d("ANIM_DEBUG", "distance based duration hint: " + hint); + } + if (hint == mDurationScaleHint) { + return; + } + mDurationScaleHint = hint; + if (hint != HINT_NO_SCALE) { + int uiMode = res.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK; + DisplayMetrics metrics = res.getDisplayMetrics(); + float width = metrics.widthPixels / metrics.xdpi; + float height = metrics.heightPixels / metrics.ydpi; + float viewingDistance = AnimationUtils.getViewingDistance(width, height, uiMode); + if (ANIM_DEBUG) { + Log.d("ANIM_DEBUG", "width, height, viewing distance, uimode: " + + width + ", " + height + ", " + viewingDistance + ", " + uiMode); + } + mScreenSizeBasedDurationScale = AnimationUtils + .getScreenSizeBasedDurationScale(width, height, viewingDistance); + mDpBasedDurationScale = AnimationUtils.getDpBasedDurationScale( + metrics.density, metrics.xdpi, viewingDistance); + if (ANIM_DEBUG) { + Log.d("ANIM_DEBUG", "screen based scale, dp based scale: " + + mScreenSizeBasedDurationScale + ", " + mDpBasedDurationScale); + } + } + } + + // Copies duration scale hint and scaling factors to the new animation. + void copyDurationScaleInfoTo(Animator anim) { + anim.mDurationScaleHint = mDurationScaleHint; + anim.mScreenSizeBasedDurationScale = mScreenSizeBasedDurationScale; + anim.mDpBasedDurationScale = mDpBasedDurationScale; + } + + /** + * @return The scaled duration calculated based on distance of movement (as defined by the + * animation) and perceived velocity (derived from the duration set on the animation for + * baseline device) + */ + public long getDistanceBasedDuration() { + return (long) (getDuration() * getDistanceBasedDurationScale()); + } + + /** + * @return scaling factor of duration based on the duration scale hint. A scaling factor of 1 + * means no scaling will be applied to the duration. + */ + float getDistanceBasedDurationScale() { + if (mDurationScaleHint == HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE) { + return mScreenSizeBasedDurationScale; + } else if (mDurationScaleHint == HINT_DISTANCE_DEFINED_IN_DP) { + return mDpBasedDurationScale; + } else { + return 1f; + } + } + + /** * The time interpolator used in calculating the elapsed fraction of the * animation. The interpolator determines whether the animation runs with * linear or non-linear motion, such as acceleration and deceleration. The diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 4a9ba3b..df5a4cb 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -70,6 +70,13 @@ public class AnimatorInflater { private static final int VALUE_TYPE_COLOR = 3; private static final int VALUE_TYPE_UNDEFINED = 4; + /** + * Enum values used in XML attributes to indicate the duration scale hint. + */ + private static final int HINT_NO_SCALE = 0; + private static final int HINT_PROPORTIONAL_TO_SCREEN = 1; + private static final int HINT_DEFINED_IN_DP = 2; + private static final boolean DBG_ANIMATOR_INFLATER = false; // used to calculate changing configs for resource references @@ -691,6 +698,9 @@ public class AnimatorInflater { int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER); createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering, pixelSize); + final int hint = a.getInt(R.styleable.Animator_durationScaleHint, + HINT_NO_SCALE); + anim.setDurationScaleHint(hint, res); a.recycle(); } else if (name.equals("propertyValuesHolder")) { PropertyValuesHolder[] values = loadValues(res, theme, parser, @@ -1027,6 +1037,9 @@ public class AnimatorInflater { anim.setInterpolator(interpolator); } + final int hint = arrayAnimator.getInt(R.styleable.Animator_durationScaleHint, + HINT_NO_SCALE); + anim.setDurationScaleHint(hint, res); arrayAnimator.recycle(); if (arrayObjectAnimator != null) { arrayObjectAnimator.recycle(); diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 53d5237..dd5f18e 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -519,6 +519,7 @@ public final class AnimatorSet extends Animator { for (Node node : mNodes) { node.animation.setAllowRunningAsynchronously(false); + copyDurationScaleInfoTo(node.animation); } if (mDuration >= 0) { diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 2386007..275e78e 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -17,9 +17,12 @@ package android.animation; import android.annotation.CallSuper; +import android.content.res.Configuration; +import android.content.res.Resources; import android.os.Looper; import android.os.Trace; import android.util.AndroidRuntimeException; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Choreographer; import android.view.animation.AccelerateDecelerateInterpolator; @@ -561,7 +564,7 @@ public class ValueAnimator extends Animator { } private void updateScaledDuration() { - mDuration = (long)(mUnscaledDuration * sDurationScale); + mDuration = (long)(mUnscaledDuration * sDurationScale * getDistanceBasedDurationScale()); } /** @@ -1478,6 +1481,12 @@ public class ValueAnimator extends Animator { anim.mInitialized = false; anim.mPlayingState = STOPPED; anim.mStartedDelay = false; + anim.mStarted = false; + anim.mRunning = false; + anim.mPaused = false; + anim.mResumed = false; + anim.mStartListenersCalled = false; + PropertyValuesHolder[] oldValues = mValues; if (oldValues != null) { int numValues = oldValues.length; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b5817df..6cf6481 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -89,11 +89,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.PhoneWindow; +import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewManager; +import android.view.ViewRootImpl; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -786,6 +788,8 @@ public class Activity extends ContextThemeWrapper private TranslucentConversionListener mTranslucentCallback; private boolean mChangeCanvasToTranslucent; + private SearchEvent mSearchEvent; + private boolean mTitleReady = false; private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY; @@ -3546,12 +3550,23 @@ public class Activity extends ContextThemeWrapper * implementation changes to simply return false and you must supply your own custom * implementation if you want to support search.</p> * + * @param searchEvent The {@link SearchEvent} that signaled this search. * @return Returns {@code true} if search launched, and {@code false} if the activity does * not respond to search. The default implementation always returns {@code true}, except * when in {@link Configuration#UI_MODE_TYPE_TELEVISION} mode where it returns false. * * @see android.app.SearchManager */ + public boolean onSearchRequested(@Nullable SearchEvent searchEvent) { + mSearchEvent = searchEvent; + boolean result = onSearchRequested(); + mSearchEvent = null; + return result; + } + + /** + * @see #onSearchRequested(SearchEvent) + */ public boolean onSearchRequested() { if ((getResources().getConfiguration().uiMode&Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_TELEVISION) { @@ -3563,6 +3578,17 @@ public class Activity extends ContextThemeWrapper } /** + * During the onSearchRequested() callbacks, this function will return the + * {@link SearchEvent} that triggered the callback, if it exists. + * + * @return SearchEvent The SearchEvent that triggered the {@link + * #onSearchRequested} callback. + */ + public final SearchEvent getSearchEvent() { + return mSearchEvent; + } + + /** * This hook is called to launch the search UI. * * <p>It is typically called from onSearchRequested(), either directly from @@ -4467,21 +4493,38 @@ public class Activity extends ContextThemeWrapper */ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) { + startActivityForResult(fragment.mWho, intent, requestCode, options); + } + + /** + * @hide + */ + @Override + public void startActivityForResult( + String who, Intent intent, int requestCode, @Nullable Bundle options) { if (options != null) { mActivityTransitionState.startExitOutTransition(this, options); } Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( - this, mMainThread.getApplicationThread(), mToken, fragment, + this, mMainThread.getApplicationThread(), mToken, who, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( - mToken, fragment.mWho, requestCode, + mToken, who, requestCode, ar.getResultCode(), ar.getResultData()); } } /** + * @hide + */ + @Override + public boolean canStartActivityForResult() { + return true; + } + + /** * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender, * int, Intent, int, int, int, Bundle)} with no options. */ @@ -6364,12 +6407,24 @@ public class Activity extends ContextThemeWrapper onActivityResult(requestCode, resultCode, data); } } else { - Fragment frag = mFragments.findFragmentByWho(who); - if (frag != null) { - if (isRequestPermissionResult(data)) { - dispatchRequestPermissionsResultToFragment(requestCode, data, frag); - } else { - frag.onActivityResult(requestCode, resultCode, data); + if (who.startsWith("@android:view:")) { + ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( + getActivityToken()); + for (ViewRootImpl viewRoot : views) { + if (viewRoot.getView() != null + && viewRoot.getView().dispatchActivityResult( + who, requestCode, resultCode, data)) { + return; + } + } + } else { + Fragment frag = mFragments.findFragmentByWho(who); + if (frag != null) { + if (isRequestPermissionResult(data)) { + dispatchRequestPermissionsResultToFragment(requestCode, data, frag); + } else { + frag.onActivityResult(requestCode, resultCode, data); + } } } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3b96fd5..10d6d01 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -220,6 +220,7 @@ public final class ActivityThread { // which means this lock gets held while the activity and window managers // holds their own lock. Thus you MUST NEVER call back into the activity manager // or window manager or anything that depends on them while holding this lock. + // These LoadedApk are only valid for the userId that we're running as. final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<String, WeakReference<LoadedApk>>(); final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages @@ -258,6 +259,8 @@ public final class ActivityThread { IActivityManager.ContentProviderHolder holder; boolean acquiring = true; int requests = 1; + // Set if there was a runtime exception when trying to acquire the provider. + RuntimeException runtimeException = null; } // The lock of mProviderMap protects the following variables. @@ -864,10 +867,10 @@ public final class ActivityThread { } public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) { - final Network network = ConnectivityManager.getProcessDefaultNetwork(); + final ConnectivityManager cm = ConnectivityManager.from(getSystemContext()); + final Network network = cm.getBoundNetworkForProcess(); if (network != null) { - Proxy.setHttpProxySystemProperty( - ConnectivityManager.from(getSystemContext()).getDefaultProxy()); + Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); } else { Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } @@ -1705,13 +1708,18 @@ public final class ActivityThread { public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, int flags, int userId) { + final boolean differentUser = (UserHandle.myUserId() != userId); synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; - if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) { + if (differentUser) { + // Caching not supported across users + ref = null; + } else if ((flags & Context.CONTEXT_INCLUDE_CODE) != 0) { ref = mPackages.get(packageName); } else { ref = mResourcePackages.get(packageName); } + LoadedApk packageInfo = ref != null ? ref.get() : null; //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir @@ -1791,13 +1799,18 @@ public final class ActivityThread { private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { + final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; - if (includeCode) { + if (differentUser) { + // Caching not supported across users + ref = null; + } else if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } + LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { @@ -1816,7 +1829,9 @@ public final class ActivityThread { getSystemContext().mPackageInfo.getClassLoader()); } - if (includeCode) { + if (differentUser) { + // Caching not supported across users + } else if (includeCode) { mPackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); } else { @@ -4715,39 +4730,55 @@ public final class ActivityThread { } IActivityManager.ContentProviderHolder holder = null; - if (first) { - // Multiple threads may try to acquire the same provider at the same time. - // When this happens, we only let the first one really gets provider. - // Other threads just wait for its result. - // Note that we cannot hold the lock while acquiring and installing the - // provider since it might take a long time to run and it could also potentially - // be re-entrant in the case where the provider is in the same process. - try { + try { + if (first) { + // Multiple threads may try to acquire the same provider at the same time. + // When this happens, we only let the first one really gets provider. + // Other threads just wait for its result. + // Note that we cannot hold the lock while acquiring and installing the + // provider since it might take a long time to run and it could also potentially + // be re-entrant in the case where the provider is in the same process. holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable); - } catch (RemoteException ex) { + } else { + synchronized (r) { + while (r.acquiring) { + try { + r.wait(); + } catch (InterruptedException e) { + } + } + holder = r.holder; + } } + } catch (RemoteException ex) { + } catch (RuntimeException e) { synchronized (r) { - r.holder = holder; - r.acquiring = false; - r.notifyAll(); + r.runtimeException = e; } - } else { - synchronized (r) { - while (r.acquiring) { - try { - r.wait(); - } catch (InterruptedException e) { - } + } finally { + if (first) { + synchronized (r) { + r.holder = holder; + r.acquiring = false; + r.notifyAll(); } - holder = r.holder; } - } - synchronized (mAcquiringProviderMap) { - if (--r.requests == 0) { - mAcquiringProviderMap.remove(key); + + synchronized (mAcquiringProviderMap) { + if (--r.requests == 0) { + mAcquiringProviderMap.remove(key); + } + } + + if (r.runtimeException != null) { + // Was set when the first thread tried to acquire the provider, + // but we should make sure it is thrown for all threads trying to + // acquire the provider. + throw r.runtimeException; } } + if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); return null; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ffdc81d..9ddfd88 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1359,6 +1359,26 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public String getDefaultBrowserPackageName(int userId) { + try { + return mPM.getDefaultBrowserPackageName(userId); + } catch (RemoteException e) { + // Should never happen! + return null; + } + } + + @Override + public boolean setDefaultBrowserPackageName(String packageName, int userId) { + try { + return mPM.setDefaultBrowserPackageName(packageName, userId); + } catch (RemoteException e) { + // Should never happen! + return false; + } + } + + @Override public void setInstallerPackageName(String targetPackage, String installerPackageName) { try { diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 83451aa..8fb048b 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -1368,7 +1368,6 @@ final class BackStackRecord extends FragmentTransaction implements public boolean onPreDraw() { sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); if (enterTransition != null) { - enterTransition.removeTarget(nonExistingView); removeTargets(enterTransition, enteringViews); } if (exitTransition != null) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 786a52f..d049104 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -49,6 +49,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.PhoneWindow; +import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; @@ -123,6 +124,8 @@ public class Dialog implements DialogInterface, Window.Callback, private Handler mListenersHandler; + private SearchEvent mSearchEvent; + private ActionMode mActionMode; private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY; @@ -995,6 +998,14 @@ public class Dialog implements DialogInterface, Window.Callback, /** * This hook is called when the user signals the desire to start a search. */ + public boolean onSearchRequested(SearchEvent searchEvent) { + mSearchEvent = searchEvent; + return onSearchRequested(); + } + + /** + * This hook is called when the user signals the desire to start a search. + */ public boolean onSearchRequested() { final SearchManager searchManager = (SearchManager) mContext .getSystemService(Context.SEARCH_SERVICE); @@ -1011,6 +1022,17 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * During the onSearchRequested() callbacks, this function will return the + * {@link SearchEvent} that triggered the callback, if it exists. + * + * @return SearchEvent The SearchEvent that triggered the {@link + * #onSearchRequested} callback. + */ + public final SearchEvent getSearchEvent() { + return mSearchEvent; + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl index 845897b..caee14f 100644 --- a/core/java/android/app/IUserSwitchObserver.aidl +++ b/core/java/android/app/IUserSwitchObserver.aidl @@ -22,4 +22,5 @@ import android.os.IRemoteCallback; oneway interface IUserSwitchObserver { void onUserSwitching(int newUserId, IRemoteCallback reply); void onUserSwitchComplete(int newUserId); + void onForegroundProfileSwitch(int newProfileId); } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 5572d30..fc96464 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1568,7 +1568,7 @@ public class Instrumentation { /** * Like {@link #execStartActivity(android.content.Context, android.os.IBinder, - * android.os.IBinder, Fragment, android.content.Intent, int, android.os.Bundle)}, + * android.os.IBinder, String, android.content.Intent, int, android.os.Bundle)}, * but for calls from a {#link Fragment}. * * @param who The Context from which the activity is being started. @@ -1576,7 +1576,7 @@ public class Instrumentation { * is being started. * @param token Internal token identifying to the system who is starting * the activity; may be null. - * @param target Which fragment is performing the start (and thus receiving + * @param target Which element is performing the start (and thus receiving * any result). * @param intent The actual Intent to start. * @param requestCode Identifier for this request's result; less than zero @@ -1595,7 +1595,7 @@ public class Instrumentation { * {@hide} */ public ActivityResult execStartActivity( - Context who, IBinder contextThread, IBinder token, Fragment target, + Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { @@ -1619,8 +1619,7 @@ public class Instrumentation { int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), - token, target != null ? target.mWho : null, - requestCode, 0, null, options); + token, target, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 83c6c2b..9604789 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -18,7 +18,6 @@ package android.app; import android.text.TextUtils; import android.util.ArrayMap; - import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -46,6 +45,7 @@ import android.util.SparseArray; import android.view.DisplayAdjustments; import android.view.Display; import android.os.SystemProperties; + import dalvik.system.VMRuntime; import java.io.File; @@ -136,10 +136,6 @@ public final class LoadedApk { mSplitAppDirs = aInfo.splitSourceDirs; mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs; mOverlayDirs = aInfo.resourceDirs; - if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) { - aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid), - mPackageName); - } mSharedLibraries = aInfo.sharedLibraryFiles; mDataDir = aInfo.dataDir; mDataDirFile = mDataDir != null ? new File(mDataDir) : null; diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index cf14202..2cfc1fa4 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -151,6 +151,14 @@ public final class PendingIntent implements Parcelable { public static final int FLAG_UPDATE_CURRENT = 1<<27; /** + * Flag indicating that the created PendingIntent should be immutable. + * This means that the additional intent argument passed to the send + * methods to fill in unpopulated properties of this intent will be + * ignored. + */ + public static final int FLAG_IMMUTABLE = 1<<26; + + /** * Exception thrown when trying to send through a PendingIntent that * has been canceled or is otherwise no longer able to execute the request. */ @@ -618,7 +626,8 @@ public final class PendingIntent implements Parcelable { * @param code Result code to supply back to the PendingIntent's target. * @param intent Additional Intent data. See {@link Intent#fillIn * Intent.fillIn()} for information on how this is applied to the - * original Intent. + * original Intent. If flag {@link #FLAG_IMMUTABLE} was set when this + * pending intent was created, this argument will be ignored. * * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) * @@ -667,6 +676,8 @@ public final class PendingIntent implements Parcelable { * @param intent Additional Intent data. See {@link Intent#fillIn * Intent.fillIn()} for information on how this is applied to the * original Intent. Use null to not modify the original Intent. + * If flag {@link #FLAG_IMMUTABLE} was set when this pending intent was + * created, this argument will be ignored. * @param onFinished The object to call back on when the send has * completed, or null for no callback. * @param handler Handler identifying the thread on which the callback @@ -703,6 +714,8 @@ public final class PendingIntent implements Parcelable { * @param intent Additional Intent data. See {@link Intent#fillIn * Intent.fillIn()} for information on how this is applied to the * original Intent. Use null to not modify the original Intent. + * If flag {@link #FLAG_IMMUTABLE} was set when this pending intent was + * created, this argument will be ignored. * @param onFinished The object to call back on when the send has * completed, or null for no callback. * @param handler Handler identifying the thread on which the callback diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java index e58b7fb..bac84a4 100644 --- a/core/java/android/app/SharedElementCallback.java +++ b/core/java/android/app/SharedElementCallback.java @@ -51,9 +51,23 @@ public abstract class SharedElementCallback { }; /** - * Called immediately after the start state is set for the shared element. - * The shared element will start at the size and position of the shared element - * in the launching Activity or Fragment. + * In Activity Transitions, onSharedElementStart is called immediately before + * capturing the start of the shared element state on enter and reenter transitions and + * immediately before capturing the end of the shared element state for exit and return + * transitions. + * <p> + * In Fragment Transitions, onSharedElementStart is called immediately before capturing the + * start state of all shared element transitions. + * <p> + * This call can be used to adjust the transition start state by modifying the shared + * element Views. Note that no layout step will be executed between onSharedElementStart + * and the transition state capture. + * <p> + * For Activity Transitions, any changes made in {@link #onSharedElementEnd(List, List, List)} + * that are not updated during by layout should be corrected in onSharedElementStart for exit and + * return transitions. For example, rotation or scale will not be affected by layout and + * if changed in {@link #onSharedElementEnd(List, List, List)}, it will also have to be reset + * in onSharedElementStart again to correct the end state. * * @param sharedElementNames The names of the shared elements that were accepted into * the View hierarchy. @@ -68,17 +82,23 @@ public abstract class SharedElementCallback { List<View> sharedElements, List<View> sharedElementSnapshots) {} /** - * Called after the end state is set for the shared element, but before the end state - * is captured by the shared element transition. + * In Activity Transitions, onSharedElementEnd is called immediately before + * capturing the end of the shared element state on enter and reenter transitions and + * immediately before capturing the start of the shared element state for exit and return + * transitions. * <p> - * Any customization done in - * {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} - * may need to be modified to the final state of the shared element if it is not - * automatically corrected by layout. For example, rotation or scale will not - * be affected by layout and if changed in {@link #onSharedElementStart(java.util.List, - * java.util.List, java.util.List)}, it will also have to be set here again to correct - * the end state. - * </p> + * In Fragment Transitions, onSharedElementEnd is called immediately before capturing the + * end state of all shared element transitions. + * <p> + * This call can be used to adjust the transition end state by modifying the shared + * element Views. Note that no layout step will be executed between onSharedElementEnd + * and the transition state capture. + * <p> + * Any changes made in {@link #onSharedElementStart(List, List, List)} that are not updated + * during layout should be corrected in onSharedElementEnd. For example, rotation or scale + * will not be affected by layout and if changed in + * {@link #onSharedElementStart(List, List, List)}, it will also have to be reset in + * onSharedElementEnd again to correct the end state. * * @param sharedElementNames The names of the shared elements that were accepted into * the View hierarchy. diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index edb768d..2b3cf34 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -995,6 +995,24 @@ public final class BluetoothAdapter { } /** + * Return true if hardware has entries available for matching beacons + * + * @return true if there are hw entries available for matching beacons + * @hide + */ + public boolean isHardwareTrackingFiltersAvailable() { + if (getState() != STATE_ON) return false; + try { + synchronized(mManagerCallback) { + if(mService != null) return (mService.numOfHwTrackFiltersAvailable() != 0); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; + } + + /** * Return the record of {@link BluetoothActivityEnergyInfo} object that * has the activity and energy info. This can be used to ascertain what * the controller has been up to, since the last sample. diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index dabb1ce..299f4c8 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -98,6 +98,7 @@ interface IBluetooth boolean isActivityAndEnergyReportingSupported(); void getActivityEnergyInfoFromController(); BluetoothActivityEnergyInfo reportActivityInfo(); + int numOfHwTrackFiltersAvailable(); // for dumpsys support String dump(); diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java index 5dbfa6a..b6bcfb8 100644 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -128,6 +128,16 @@ public final class BluetoothLeScanner { ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); return; } + if (!isHardwareResourcesAvailableForScan(settings)) { + postCallbackError(callback, + ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); + return; + } + if (!isSettingsAndFilterComboAllowed(settings, filters)) { + postCallbackError(callback, + ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); + return; + } BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, settings, callback, resultStorages); wrapper.startRegisteration(); @@ -394,4 +404,33 @@ public final class BluetoothLeScanner { } return false; } + + private boolean isSettingsAndFilterComboAllowed(ScanSettings settings, + List <ScanFilter> filterList) { + final int callbackType = settings.getCallbackType(); + // If onlost/onfound is requested, a non-empty filter is expected + if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH + | ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { + if (filterList == null) { + return false; + } + for (ScanFilter filter : filterList) { + if (filter.isAllFieldsEmpty()) { + return false; + } + } + } + return true; + } + + private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { + final int callbackType = settings.getCallbackType(); + if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 + || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { + // For onlost/onfound, we required hw support be available + return (mBluetoothAdapter.isOffloadedFilteringSupported() && + mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); + } + return true; + } } diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java index 05782a8..27b96bd 100644 --- a/core/java/android/bluetooth/le/ScanCallback.java +++ b/core/java/android/bluetooth/le/ScanCallback.java @@ -45,10 +45,16 @@ public abstract class ScanCallback { public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; /** + * Fails to start scan as it is out of hardware resources. + * @hide + */ + public static final int SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES = 5; + + /** * Callback when a BLE advertisement has been found. * - * @param callbackType Determines how this callback was triggered. Currently could only be - * {@link ScanSettings#CALLBACK_TYPE_ALL_MATCHES}. + * @param callbackType Determines how this callback was triggered. Could be of + * {@link ScanSettings#CALLBACK_TYPE_ALL_MATCHES} * @param result A Bluetooth LE scan result. */ public void onScanResult(int callbackType, ScanResult result) { diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java index 5025218..92a3817 100644 --- a/core/java/android/bluetooth/le/ScanFilter.java +++ b/core/java/android/bluetooth/le/ScanFilter.java @@ -67,6 +67,8 @@ public final class ScanFilter implements Parcelable { private final byte[] mManufacturerData; @Nullable private final byte[] mManufacturerDataMask; + private static final ScanFilter EMPTY = new ScanFilter.Builder().build() ; + private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, ParcelUuid uuidMask, ParcelUuid serviceDataUuid, @@ -410,6 +412,14 @@ public final class ScanFilter implements Parcelable { } /** + * Checks if the scanfilter is empty + * @hide + */ + public boolean isAllFieldsEmpty() { + return EMPTY.equals(this); + } + + /** * Builder class for {@link ScanFilter}. */ public static final class Builder { diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java index 0106686..f103cae 100644 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -59,7 +59,6 @@ public final class ScanSettings implements Parcelable { /** * A result callback is only triggered for the first advertisement packet received that matches * the filter criteria. - * * @hide */ @SystemApi @@ -68,15 +67,53 @@ public final class ScanSettings implements Parcelable { /** * Receive a callback when advertisements are no longer received from a device that has been * previously reported by a first match callback. - * * @hide */ @SystemApi public static final int CALLBACK_TYPE_MATCH_LOST = 4; + + /** + * Determines how many advertisements to match per filter, as this is scarce hw resource + */ + /** + * Match one advertisement per filter + * @hide + */ + public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; + + /** + * Match few advertisement per filter, depends on current capability and availibility of + * the resources in hw + * @hide + */ + public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; + + /** + * Match as many advertisement per filter as hw could allow, depends on current + * capability and availibility of the resources in hw + * @hide + */ + public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; + + + /** + * In Aggressive mode, hw will determine a match sooner even with feeble signal strength + * and few number of sightings/match in a duration. + * @hide + */ + public static final int MATCH_MODE_AGGRESSIVE = 1; + /** - * Request full scan results which contain the device, rssi, advertising data, scan response as - * well as the scan timestamp. + * For sticky mode, higher threshold of signal strength and sightings is required + * before reporting by hw + * @hide + */ + public static final int MATCH_MODE_STICKY = 2; + + /** + * Request full scan results which contain the device, rssi, advertising data, scan response + * as well as the scan timestamp. * * @hide */ @@ -106,6 +143,10 @@ public final class ScanSettings implements Parcelable { // Time of delay for reporting the scan result private long mReportDelayMillis; + private int mMatchMode; + + private int mNumOfMatchesPerFilter; + public int getScanMode() { return mScanMode; } @@ -119,6 +160,20 @@ public final class ScanSettings implements Parcelable { } /** + * @hide + */ + public int getMatchMode() { + return mMatchMode; + } + + /** + * @hide + */ + public int getNumOfMatches() { + return mNumOfMatchesPerFilter; + } + + /** * Returns report delay timestamp based on the device clock. */ public long getReportDelayMillis() { @@ -126,11 +181,13 @@ public final class ScanSettings implements Parcelable { } private ScanSettings(int scanMode, int callbackType, int scanResultType, - long reportDelayMillis) { + long reportDelayMillis, int matchMode, int numOfMatchesPerFilter) { mScanMode = scanMode; mCallbackType = callbackType; mScanResultType = scanResultType; mReportDelayMillis = reportDelayMillis; + mNumOfMatchesPerFilter = numOfMatchesPerFilter; + mMatchMode = numOfMatchesPerFilter; } private ScanSettings(Parcel in) { @@ -138,6 +195,8 @@ public final class ScanSettings implements Parcelable { mCallbackType = in.readInt(); mScanResultType = in.readInt(); mReportDelayMillis = in.readLong(); + mMatchMode = in.readInt(); + mNumOfMatchesPerFilter = in.readInt(); } @Override @@ -146,6 +205,8 @@ public final class ScanSettings implements Parcelable { dest.writeInt(mCallbackType); dest.writeInt(mScanResultType); dest.writeLong(mReportDelayMillis); + dest.writeInt(mMatchMode); + dest.writeInt(mNumOfMatchesPerFilter); } @Override @@ -174,7 +235,8 @@ public final class ScanSettings implements Parcelable { private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES; private int mScanResultType = SCAN_RESULT_TYPE_FULL; private long mReportDelayMillis = 0; - + private int mMatchMode = MATCH_MODE_AGGRESSIVE; + private int mNumOfMatchesPerFilter = MATCH_NUM_ONE_ADVERTISEMENT; /** * Set scan mode for Bluetooth LE scan. * @@ -255,11 +317,48 @@ public final class ScanSettings implements Parcelable { } /** + * Set the number of matches for Bluetooth LE scan filters hardware match + * + * @param numOfMatches The num of matches can be one of + * {@link ScanSettings#MATCH_NUM_ONE_ADVERTISEMENT} or + * {@link ScanSettings#MATCH_NUM_FEW_ADVERTISEMENT} or + * {@link ScanSettings#MATCH_NUM_MAX_ADVERTISEMENT} + * @throws IllegalArgumentException If the {@code matchMode} is invalid. + * @hide + */ + public Builder setNumOfMatches(int numOfMatches) { + if (numOfMatches < MATCH_NUM_ONE_ADVERTISEMENT + || numOfMatches > MATCH_NUM_MAX_ADVERTISEMENT) { + throw new IllegalArgumentException("invalid numOfMatches " + numOfMatches); + } + mNumOfMatchesPerFilter = numOfMatches; + return this; + } + + /** + * Set match mode for Bluetooth LE scan filters hardware match + * + * @param matchMode The match mode can be one of + * {@link ScanSettings#MATCH_MODE_AGGRESSIVE} or + * {@link ScanSettings#MATCH_MODE_STICKY} + * @throws IllegalArgumentException If the {@code matchMode} is invalid. + * @hide + */ + public Builder setMatchMode(int matchMode) { + if (matchMode < MATCH_MODE_AGGRESSIVE + || matchMode > MATCH_MODE_STICKY) { + throw new IllegalArgumentException("invalid matchMode " + matchMode); + } + mMatchMode = matchMode; + return this; + } + + /** * Build {@link ScanSettings}. */ public ScanSettings build() { return new ScanSettings(mScanMode, mCallbackType, mScanResultType, - mReportDelayMillis); + mReportDelayMillis, mMatchMode, mNumOfMatchesPerFilter); } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9450dce..e5e55d6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1298,6 +1298,34 @@ public abstract class Context { } /** + * Version of {@link #startActivity(Intent, Bundle)} that returns a result to the caller. This + * is only supported for Views and Fragments. + * @param who The identifier for the calling element that will receive the result. + * @param intent The intent to start. + * @param requestCode The code that will be returned with onActivityResult() identifying this + * request. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * @hide + */ + public void startActivityForResult( + @NonNull String who, Intent intent, int requestCode, @Nullable Bundle options) { + throw new RuntimeException("This method is only implemented for Activity-based Contexts. " + + "Check canStartActivityForResult() before calling."); + } + + /** + * Identifies whether this Context instance will be able to process calls to + * {@link #startActivityForResult(String, Intent, int, Bundle)}. + * @hide + */ + public boolean canStartActivityForResult() { + return false; + } + + /** * Same as {@link #startActivities(Intent[], Bundle)} with no options * specified. * @@ -2161,7 +2189,7 @@ public abstract class Context { WIFI_RTT_SERVICE, NSD_SERVICE, AUDIO_SERVICE, - //@hide: FINGERPRINT_SERVICE, + FINGERPRINT_SERVICE, MEDIA_ROUTER_SERVICE, TELEPHONY_SERVICE, TELEPHONY_SUBSCRIPTION_SERVICE, @@ -2668,12 +2696,11 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.service.fingerprint.FingerprintManager} for handling management + * {@link android.hardware.fingerprint.FingerprintManager} for handling management * of fingerprints. * * @see #getSystemService - * @see android.service.fingerprint.FingerprintManager - * @hide + * @see android.hardware.fingerprint.FingerprintManager */ public static final String FINGERPRINT_SERVICE = "fingerprint"; diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 8c5a87c..92f0079 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -336,6 +336,17 @@ public class ContextWrapper extends Context { mBase.startActivityAsUser(intent, user); } + /** @hide **/ + public void startActivityForResult( + String who, Intent intent, int requestCode, Bundle options) { + mBase.startActivityForResult(who, intent, requestCode, options); + } + + /** @hide **/ + public boolean canStartActivityForResult() { + return mBase.canStartActivityForResult(); + } + @Override public void startActivity(Intent intent, Bundle options) { mBase.startActivity(intent, options); diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 044e3e3..33c0b87 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -521,14 +521,18 @@ public class IntentFilter implements Parcelable { * * @return True if the filter handle all HTTP or HTTPS data URI. False otherwise. * - * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and + * This will check if: + * + * - either the Intent category is {@link android.content.Intent#CATEGORY_APP_BROWSER} + * - either the Intent action is {@link android.content.Intent#ACTION_VIEW} and * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent * data scheme is "http" or "https" and that there is no specific host defined. * * @hide */ public final boolean handleAllWebDataURI() { - return hasWebDataURI() && (countDataAuthorities() == 0); + return hasCategory(Intent.CATEGORY_APP_BROWSER) || + (hasWebDataURI() && countDataAuthorities() == 0); } /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 55c990f..c2580c0 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -450,6 +450,9 @@ interface IPackageManager { List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName); List<IntentFilter> getAllIntentFilters(String packageName); + boolean setDefaultBrowserPackageName(String packageName, int userId); + String getDefaultBrowserPackageName(int userId); + VerifierDeviceIdentity getVerifierDeviceIdentity(); boolean isFirstBoot(); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index d5461eb..8844ea8 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -44,6 +44,7 @@ import android.os.Environment; import android.os.RemoteException; import android.os.UserHandle; import android.os.storage.VolumeInfo; +import android.text.TextUtils; import android.util.AndroidException; import com.android.internal.util.ArrayUtils; @@ -3667,6 +3668,32 @@ public abstract class PackageManager { public abstract List<IntentFilter> getAllIntentFilters(String packageName); /** + * Get the default Browser package name for a specific user. + * + * @param userId The user id. + * + * @return the package name of the default Browser for the specified user. If the user id passed + * is -1 (all users) it will return a null value. + * + * @hide + */ + public abstract String getDefaultBrowserPackageName(int userId); + + /** + * Set the default Browser package name for a specific user. + * + * @param packageName The package name of the default Browser. + * @param userId The user id. + * + * @return true if the default Browser for the specified user has been set, + * otherwise return false. If the user id passed is -1 (all users) this call will not + * do anything and just return false. + * + * @hide + */ + public abstract boolean setDefaultBrowserPackageName(String packageName, int userId); + + /** * Change the installer associated with a given package. There are limitations * on how the installer package can be changed; in particular: * <ul> @@ -4158,16 +4185,19 @@ public abstract class PackageManager { public abstract @NonNull PackageInstaller getPackageInstaller(); /** - * Returns the data directory for a particular user and package, given the uid of the package. - * @param uid uid of the package, including the userId and appId - * @param packageName name of the package - * @return the user-specific data directory for the package + * Returns the data directory for a particular package and user. + * * @hide */ - public static String getDataDirForUser(int userId, String packageName) { + public static File getDataDirForUser(String volumeUuid, String packageName, int userId) { // TODO: This should be shared with Installer's knowledge of user directory - return Environment.getDataDirectory().toString() + "/user/" + userId - + "/" + packageName; + final File base; + if (TextUtils.isEmpty(volumeUuid)) { + base = Environment.getDataDirectory(); + } else { + base = new File("/mnt/expand/" + volumeUuid); + } + return new File(base, "user/" + userId + "/" + packageName); } /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 7523675..763a017 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -111,6 +111,9 @@ public class PackageParser { /** File name in an APK for the Android manifest. */ private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; + /** Path prefix for apps on expanded storage */ + private static final String MNT_EXPAND = "/mnt/expand/"; + /** @hide */ public static class NewPermissionInfo { public final String name; @@ -860,6 +863,12 @@ public class PackageParser { throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); + String volumeUuid = null; + if (apkPath.startsWith(MNT_EXPAND)) { + final int end = apkPath.indexOf('/', MNT_EXPAND.length()); + volumeUuid = apkPath.substring(MNT_EXPAND.length(), end); + } + mParseError = PackageManager.INSTALL_SUCCEEDED; mArchiveSourcePath = apkFile.getAbsolutePath(); @@ -882,6 +891,7 @@ public class PackageParser { apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]); } + pkg.volumeUuid = volumeUuid; pkg.baseCodePath = apkPath; pkg.mSignatures = null; @@ -4206,6 +4216,8 @@ public class PackageParser { // TODO: work towards making these paths invariant + public String volumeUuid; + /** * Path where this package was found on disk. For monolithic packages * this is path to single base APK file; for cluster packages this is @@ -4727,7 +4739,8 @@ public class PackageParser { ApplicationInfo ai = new ApplicationInfo(p.applicationInfo); if (userId != 0) { ai.uid = UserHandle.getUid(userId, ai.uid); - ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName); + ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId) + .getAbsolutePath(); } if ((flags & PackageManager.GET_META_DATA) != 0) { ai.metaData = p.mAppMetaData; @@ -4755,7 +4768,8 @@ public class PackageParser { ai = new ApplicationInfo(ai); if (userId != 0) { ai.uid = UserHandle.getUid(userId, ai.uid); - ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName); + ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId) + .getAbsolutePath(); } if (state.stopped) { ai.flags |= ApplicationInfo.FLAG_STOPPED; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index bfe4aa2..f01c540 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -939,6 +939,129 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Integer>("android.lens.facing", int.class); /** + * <p>The orientation of the camera relative to the sensor + * coordinate system.</p> + * <p>The four coefficients that describe the quarternion + * rotation from the Android sensor coordinate system to a + * camera-aligned coordinate system where the X-axis is + * aligned with the long side of the image sensor, the Y-axis + * is aligned with the short side of the image sensor, and + * the Z-axis is aligned with the optical axis of the sensor.</p> + * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code> + * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation + * amount <code>theta</code>, the following formulas can be used:</p> + * <pre><code> theta = 2 * acos(w) + * a_x = x / sin(theta/2) + * a_y = y / sin(theta/2) + * a_z = z / sin(theta/2) + * </code></pre> + * <p>To create a 3x3 rotation matrix that applies the rotation + * defined by this quarternion, the following matrix can be + * used:</p> + * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw, + * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw, + * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ] + * </code></pre> + * <p>This matrix can then be used to apply the rotation to a + * column vector point with</p> + * <p><code>p' = Rp</code></p> + * <p>where <code>p</code> is in the device sensor coordinate system, and + * <code>p'</code> is in the camera-oriented coordinate system.</p> + * <p><b>Units</b>: + * Quarternion coefficients</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_POSE_ROTATION = + new Key<float[]>("android.lens.poseRotation", float[].class); + + /** + * <p>Position of the camera optical center.</p> + * <p>As measured in the device sensor coordinate system, the + * position of the camera device's optical center, as a + * three-dimensional vector <code>(x,y,z)</code>.</p> + * <p>To transform a world position to a camera-device centered + * coordinate system, the position must be translated by this + * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> + * <p><b>Units</b>: Meters</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + */ + @PublicKey + public static final Key<float[]> LENS_POSE_TRANSLATION = + new Key<float[]>("android.lens.poseTranslation", float[].class); + + /** + * <p>The parameters for this camera device's intrinsic + * calibration.</p> + * <p>The five calibration parameters that describe the + * transform from camera-centric 3D coordinates to sensor + * pixel coordinates:</p> + * <pre><code>[f_x, f_y, c_x, c_y, s] + * </code></pre> + * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical + * focal lengths, <code>[c_x, c_y]</code> is the position of the optical + * axis, and <code>s</code> is a skew parameter for the sensor plane not + * being aligned with the lens plane.</p> + * <p>These are typically used within a transformation matrix K:</p> + * <pre><code>K = [ f_x, s, c_x, + * 0, f_y, c_y, + * 0 0, 1 ] + * </code></pre> + * <p>which can then be combined with the camera pose rotation + * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and + * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the + * complete transform from world coordinates to pixel + * coordinates:</p> + * <pre><code>P = [ K 0 * [ R t + * 0 1 ] 0 1 ] + * </code></pre> + * <p>and with <code>p_w</code> being a point in the world coordinate system + * and <code>p_s</code> being a point in the camera active pixel array + * coordinate system, and with the mapping including the + * homogeneous division by z:</p> + * <pre><code> p_h = (x_h, y_h, z_h) = P p_w + * p_s = p_h / z_h + * </code></pre> + * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world + * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity + * (depth) in pixel coordinates.</p> + * <p><b>Units</b>: + * Pixels in the android.sensor.activeArraySize coordinate + * system.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + */ + @PublicKey + public static final Key<float[]> LENS_INTRINSIC_CALIBRATION = + new Key<float[]>("android.lens.intrinsicCalibration", float[].class); + + /** + * <p>The correction coefficients to correct for this camera device's + * radial lens distortion.</p> + * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that + * can be used to correct the lens's radial geometric + * distortion with the mapping equations:</p> + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * </code></pre> + * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code> + * at the lens optical center, and <code>[-1, 1]</code> are the edges of + * the active pixel array; and where <code>[x_c, y_c]</code> are the + * corrected normalized coordinates with radial distortion + * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p> + * <p><b>Units</b>: + * Coefficients for a 6th-degree even radial polynomial.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_RADIAL_DISTORTION = + new Key<float[]>("android.lens.radialDistortion", float[].class); + + /** * <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported * by this camera device.</p> * <p>Full-capability camera devices will always support OFF and FAST.</p> diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index e346dc2..d8f92e5 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2532,6 +2532,129 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.lens.state", int.class); /** + * <p>The orientation of the camera relative to the sensor + * coordinate system.</p> + * <p>The four coefficients that describe the quarternion + * rotation from the Android sensor coordinate system to a + * camera-aligned coordinate system where the X-axis is + * aligned with the long side of the image sensor, the Y-axis + * is aligned with the short side of the image sensor, and + * the Z-axis is aligned with the optical axis of the sensor.</p> + * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code> + * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation + * amount <code>theta</code>, the following formulas can be used:</p> + * <pre><code> theta = 2 * acos(w) + * a_x = x / sin(theta/2) + * a_y = y / sin(theta/2) + * a_z = z / sin(theta/2) + * </code></pre> + * <p>To create a 3x3 rotation matrix that applies the rotation + * defined by this quarternion, the following matrix can be + * used:</p> + * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw, + * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw, + * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ] + * </code></pre> + * <p>This matrix can then be used to apply the rotation to a + * column vector point with</p> + * <p><code>p' = Rp</code></p> + * <p>where <code>p</code> is in the device sensor coordinate system, and + * <code>p'</code> is in the camera-oriented coordinate system.</p> + * <p><b>Units</b>: + * Quarternion coefficients</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_POSE_ROTATION = + new Key<float[]>("android.lens.poseRotation", float[].class); + + /** + * <p>Position of the camera optical center.</p> + * <p>As measured in the device sensor coordinate system, the + * position of the camera device's optical center, as a + * three-dimensional vector <code>(x,y,z)</code>.</p> + * <p>To transform a world position to a camera-device centered + * coordinate system, the position must be translated by this + * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> + * <p><b>Units</b>: Meters</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + */ + @PublicKey + public static final Key<float[]> LENS_POSE_TRANSLATION = + new Key<float[]>("android.lens.poseTranslation", float[].class); + + /** + * <p>The parameters for this camera device's intrinsic + * calibration.</p> + * <p>The five calibration parameters that describe the + * transform from camera-centric 3D coordinates to sensor + * pixel coordinates:</p> + * <pre><code>[f_x, f_y, c_x, c_y, s] + * </code></pre> + * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical + * focal lengths, <code>[c_x, c_y]</code> is the position of the optical + * axis, and <code>s</code> is a skew parameter for the sensor plane not + * being aligned with the lens plane.</p> + * <p>These are typically used within a transformation matrix K:</p> + * <pre><code>K = [ f_x, s, c_x, + * 0, f_y, c_y, + * 0 0, 1 ] + * </code></pre> + * <p>which can then be combined with the camera pose rotation + * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and + * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the + * complete transform from world coordinates to pixel + * coordinates:</p> + * <pre><code>P = [ K 0 * [ R t + * 0 1 ] 0 1 ] + * </code></pre> + * <p>and with <code>p_w</code> being a point in the world coordinate system + * and <code>p_s</code> being a point in the camera active pixel array + * coordinate system, and with the mapping including the + * homogeneous division by z:</p> + * <pre><code> p_h = (x_h, y_h, z_h) = P p_w + * p_s = p_h / z_h + * </code></pre> + * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world + * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity + * (depth) in pixel coordinates.</p> + * <p><b>Units</b>: + * Pixels in the android.sensor.activeArraySize coordinate + * system.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + */ + @PublicKey + public static final Key<float[]> LENS_INTRINSIC_CALIBRATION = + new Key<float[]>("android.lens.intrinsicCalibration", float[].class); + + /** + * <p>The correction coefficients to correct for this camera device's + * radial lens distortion.</p> + * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that + * can be used to correct the lens's radial geometric + * distortion with the mapping equations:</p> + * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) + * </code></pre> + * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code> + * at the lens optical center, and <code>[-1, 1]</code> are the edges of + * the active pixel array; and where <code>[x_c, y_c]</code> are the + * corrected normalized coordinates with radial distortion + * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p> + * <p><b>Units</b>: + * Coefficients for a 6th-degree even radial polynomial.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + */ + @PublicKey + public static final Key<float[]> LENS_RADIAL_DISTORTION = + new Key<float[]>("android.lens.radialDistortion", float[].class); + + /** * <p>Mode of operation for the noise reduction algorithm.</p> * <p>The noise reduction algorithm attempts to improve image quality by removing * excessive noise added by the capture process, especially in dark conditions.</p> diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index e3572a2..6b4bc1e 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -16,11 +16,14 @@ package android.hardware.fingerprint; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManagerNative; import android.content.ContentResolver; import android.content.Context; import android.os.Binder; import android.os.CancellationSignal; +import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; @@ -29,6 +32,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; +import android.security.AndroidKeyStoreProvider; import android.util.Log; import android.util.Slog; @@ -41,7 +45,10 @@ import javax.crypto.Cipher; /** * A class that coordinates access to the fingerprint hardware. - * @hide + * <p> + * Use {@link android.content.Context#getSystemService(java.lang.String)} + * with argument {@link android.content.Context#FINGERPRINT_SERVICE} to get + * an instance of this class. */ public class FingerprintManager { @@ -53,28 +60,103 @@ public class FingerprintManager { private static final int MSG_ERROR = 103; private static final int MSG_REMOVED = 104; - // Message types. Must agree with HAL (fingerprint.h) - public static final int FINGERPRINT_ERROR = -1; - public static final int FINGERPRINT_ACQUIRED = 1; - public static final int FINGERPRINT_PROCESSED = 2; - public static final int FINGERPRINT_TEMPLATE_ENROLLING = 3; - public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; + // + // Error messages from fingerprint hardware during initilization, enrollment, authentication or + // removal. Must agree with the list in fingerprint.h + // - // Error messages. Must agree with HAL (fingerprint.h) + /** + * The hardware is unavailable. Try again later. + */ public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + + /** + * Error state returned when the sensor was unable to process the current image. + */ public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; + + /** + * Error state returned when the current request has been running too long. This is intended to + * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is + * platform and sensor-specific, but is generally on the order of 30 seconds. + */ public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + + /** + * Error state returned for operations like enrollment; the operation cannot be completed + * because there's not enough storage remaining to complete the operation. + */ public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + + /** + * The operation was canceled because the fingerprint sensor is unavailable. For example, + * this may happen when the user is switched, the device is locked or another pending operation + * prevents or disables it. + */ public static final int FINGERPRINT_ERROR_CANCELED = 5; + + /** + * The {@link FingerprintManager#remove(Fingerprint, RemovalCallback)} call failed. Typically + * this will happen when the provided fingerprint id was incorrect. + * + * @hide + */ + public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + */ public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; - // Image acquisition messages. Must agree with HAL (fingerprint.h) + // + // Image acquisition messages. Must agree with those in fingerprint.h + // + + /** + * The image acquired was good. + */ public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + + /** + * Only a partial fingerprint image was detected. During enrollment, the user should be + * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." + */ public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + + /** + * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or + * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). + */ public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + + /** + * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. + * For example, it's reasonable return this after multiple + * {@link #FINGERPRINT_ACQUIRED_INSUFFICIENT} or actual detection of dirt on the sensor + * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor + * when this is returned. + */ public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; + + /** + * The fingerprint image was unreadable due to lack of motion. This is most appropriate for + * linear array sensors that require a swipe motion. + */ public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; + + /** + * The fingerprint image was incomplete due to quick motion. While mostly appropriate for + * linear array sensors, this could also happen if the finger was moved during acquisition. + * The user should be asked to move the finger slower (linear) or leave the finger on the sensor + * longer. + */ public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; + + /** + * Hardware vendors may extend this list if there are conditions that do not fall under one of + * the above categories. Vendors are responsible for providing error strings for these errors. + */ public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; private IFingerprintService mService; @@ -85,21 +167,81 @@ public class FingerprintManager { private RemovalCallback mRemovalCallback; private CryptoObject mCryptoObject; private Fingerprint mRemovalFingerprint; - private boolean mListening; + + private class OnEnrollCancelListener implements OnCancelListener { + private long mChallenge; + + public OnEnrollCancelListener(long challenge) { + mChallenge = challenge; + } + + @Override + public void onCancel() { + cancelEnrollment(mChallenge); + } + } + + private class OnAuthenticationCancelListener implements OnCancelListener { + private CryptoObject mCrypto; + + public OnAuthenticationCancelListener(CryptoObject crypto) { + mCrypto = crypto; + } + + @Override + public void onCancel() { + cancelAuthentication(mCrypto); + } + } /** - * A wrapper class for a limited number of crypto objects supported by FingerprintManager. + * A wrapper class for the crypto objects supported by FingerprintManager. Currently the + * framework supports {@link Signature} and {@link Cipher} objects. */ public static class CryptoObject { - CryptoObject(Signature signature) { mSignature = signature; } - CryptoObject(Cipher cipher) { mCipher = cipher; } - private Signature mSignature; - private Cipher mCipher; + + CryptoObject(Signature signature) { + mSignature = signature; + mCipher = null; + } + + CryptoObject(Cipher cipher) { + mCipher = cipher; + mSignature = null; + } + + /** + * Get {@link Signature} object. + * @return {@link Signature} object or null if this doesn't contain one. + */ + public Signature getSignature() { return mSignature; } + + /** + * Get {@link Cipher} object. + * @return {@link Cipher} object or null if this doesn't contain one. + */ + public Cipher getCipher() { return mCipher; } + + /** + * @hide + * @return the opId associated with this object or 0 if none + */ + public long getOpId() { + if (mSignature != null) { + return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mSignature); + } else if (mCipher != null) { + return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCipher); + } + return 0; + } + + private final Signature mSignature; + private final Cipher mCipher; }; /** * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, - * AuthenticationCallback, CancellationSignal, int)} + * CancellationSignal, AuthenticationCallback, int)}. */ public static final class AuthenticationResult { private Fingerprint mFingerprint; @@ -113,14 +255,14 @@ public class FingerprintManager { /** * Obtain the crypto object associated with this transaction * @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject, - * AuthenticationCallback, CancellationSignal, int)} + * CancellationSignal, AuthenticationCallback, int)}. */ public CryptoObject getCryptoObject() { return mCryptoObject; } /** - * Obtain the Fingerprint associated with this operation. Applications are discouraged - * from associating specific fingers with specific applications or operations. Hence this - * is not public. + * Obtain the Fingerprint associated with this operation. Applications are strongly + * discouraged from associating specific fingers with specific applications or operations. + * * @hide */ public Fingerprint getFingerprint() { return mFingerprint; } @@ -128,68 +270,75 @@ public class FingerprintManager { /** * Callback structure provided to {@link FingerprintManager#authenticate(CryptoObject, - * AuthenticationCallback, CancellationSignal, int)}. Users of {@link #FingerprintManager()} - * must provide an implementation of this to {@link FingerprintManager#authenticate( - * CryptoObject, AuthenticationCallback, CancellationSignal, int) for listening to fingerprint - * events. + * CancellationSignal, AuthenticationCallback, int)}. Users of {@link + * FingerprintManager#authenticate(CryptoObject, CancellationSignal, + * AuthenticationCallback, int) } must provide an implementation of this for listening to + * fingerprint events. */ public static abstract class AuthenticationCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further callbacks will be made on this object. - * @param errMsgId an integer identifying the error message. - * @param errString a human-readible error string that can be shown in UI. + * @param errMsgId An integer identifying the error message + * @param errString A human-readable error string that can be shown in UI */ - public abstract void onAuthenticationError(int errMsgId, CharSequence errString); + public void onAuthenticationError(int errMsgId, CharSequence errString) { } /** - * Called when a recoverable error has been encountered during authentication. The help + * Called when a recoverable error has been encountered during authentication. The help * string is provided to give the user guidance for what went wrong, such as * "Sensor dirty, please clean it." - * @param helpMsgId an integer identifying the error message. - * @param helpString a human-readible string that can be shown in UI. + * @param helpMsgId An integer identifying the error message + * @param helpString A human-readable string that can be shown in UI */ - public abstract void onAuthenticationHelp(int helpMsgId, CharSequence helpString); + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { } /** * Called when a fingerprint is recognized. - * @param result an object containing authentication-related data. + * @param result An object containing authentication-related data */ - public abstract void onAuthenticationSucceeded(AuthenticationResult result); + public void onAuthenticationSucceeded(AuthenticationResult result) { } + + /** + * Called when a fingerprint is valid but not recognized. + */ + public void onAuthenticationFailed() { } }; /** * Callback structure provided to {@link FingerprintManager#enroll(long, EnrollmentCallback, * CancellationSignal, int). Users of {@link #FingerprintManager()} * must provide an implementation of this to {@link FingerprintManager#enroll(long, - * EnrollmentCallback, CancellationSignal, int) for listening to fingerprint events. + * CancellationSignal, EnrollmentCallback, int) for listening to fingerprint events. + * + * @hide */ public static abstract class EnrollmentCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further callbacks will be made on this object. - * @param errMsgId an integer identifying the error message. - * @param errString a human-readible error string that can be shown in UI. + * @param errMsgId An integer identifying the error message + * @param errString A human-readable error string that can be shown in UI */ - public abstract void onEnrollmentError(int errMsgId, CharSequence errString); + public void onEnrollmentError(int errMsgId, CharSequence errString) { } /** - * Called when a recoverable error has been encountered during enrollment. The help + * Called when a recoverable error has been encountered during enrollment. The help * string is provided to give the user guidance for what went wrong, such as * "Sensor dirty, please clean it" or what they need to do next, such as * "Touch sensor again." - * @param helpMsgId an integer identifying the error message. - * @param helpString a human-readible string that can be shown in UI. + * @param helpMsgId An integer identifying the error message + * @param helpString A human-readable string that can be shown in UI */ - public abstract void onEnrollmentHelp(int helpMsgId, CharSequence helpString); + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { } /** * Called as each enrollment step progresses. Enrollment is considered complete when - * remaining reaches 0. This function will not be called if enrollment fails. See + * remaining reaches 0. This function will not be called if enrollment fails. See * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} - * @param remaining the number of remaining steps. + * @param remaining The number of remaining steps */ - public abstract void onEnrollmentProgress(int remaining); + public void onEnrollmentProgress(int remaining) { } }; /** @@ -197,53 +346,66 @@ public class FingerprintManager { * {@link #FingerprintManager()} may optionally provide an implementation of this to * {@link FingerprintManager#remove(int, int, RemovalCallback)} for listening to * fingerprint template removal events. + * + * @hide */ public static abstract class RemovalCallback { /** * Called when the given fingerprint can't be removed. - * @param fp the fingerprint that the call attempted to remove. - * @param errMsgId an associated error message id. - * @param errString an error message indicating why the fingerprint id can't be removed. + * @param fp The fingerprint that the call attempted to remove + * @param errMsgId An associated error message id + * @param errString An error message indicating why the fingerprint id can't be removed */ - public abstract void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString); + public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { } /** * Called when a given fingerprint is successfully removed. * @param fingerprint the fingerprint template that was removed. */ - public abstract void onRemovalSucceeded(Fingerprint fingerprint); + public void onRemovalSucceeded(Fingerprint fingerprint) { } }; /** - * Request authentication of a crypto object. This call warms up the fingerprint hardware - * and starts scanning for a fingerprint. It terminates when + * Request authentication of a crypto object. This call warms up the fingerprint hardware + * and starts scanning for a fingerprint. It terminates when * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult) is called, at * which point the object is no longer valid. The operation can be canceled by using the * provided cancel object. * * @param crypto object associated with the call or null if none required. - * @param callback an object to receive authentication events * @param cancel an object that can be used to cancel authentication - * @param flags optional flags + * @param callback an object to receive authentication events + * @param flags optional flags; should be 0 */ - public void authenticate(CryptoObject crypto, AuthenticationCallback callback, - CancellationSignal cancel, int flags) { + public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, + @NonNull AuthenticationCallback callback, int flags) { if (callback == null) { throw new IllegalArgumentException("Must supply an authentication callback"); } - // TODO: handle cancel + if (cancel != null) { + if (cancel.isCanceled()) { + Log.w(TAG, "authentication already canceled"); + return; + } else { + cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto)); + } + } if (mService != null) try { mAuthenticationCallback = callback; mCryptoObject = crypto; - long sessionId = 0; // TODO: get from crypto object - startListening(); - mService.authenticate(mToken, sessionId, getCurrentUserId(), flags); + long sessionId = crypto != null ? crypto.getOpId() : 0; + mService.authenticate(mToken, sessionId, getCurrentUserId(), mServiceReceiver, flags); } catch (RemoteException e) { - Log.v(TAG, "Remote exception while authenticating: ", e); - stopListening(); + Log.w(TAG, "Remote exception while authenticating: ", e); + if (callback != null) { + // Though this may not be a hardware issue, it will cause apps to give up or try + // again later. + callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, + getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE)); + } } } @@ -257,44 +419,74 @@ public class FingerprintManager { * provided cancel object. * @param challenge a unique id provided by a recent verification of device credentials * (e.g. pin, pattern or password). - * @param callback an object to receive enrollment events * @param cancel an object that can be used to cancel enrollment + * @param callback an object to receive enrollment events * @param flags optional flags + * @hide */ - public void enroll(long challenge, EnrollmentCallback callback, - CancellationSignal cancel, int flags) { + public void enroll(long challenge, CancellationSignal cancel, EnrollmentCallback callback, + int flags) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } - // TODO: handle cancel + if (cancel != null) { + if (cancel.isCanceled()) { + Log.w(TAG, "enrollment already canceled"); + return; + } else { + cancel.setOnCancelListener(new OnEnrollCancelListener(challenge)); + } + } if (mService != null) try { mEnrollmentCallback = callback; - startListening(); - mService.enroll(mToken, getCurrentUserId(), flags); + mService.enroll(mToken, challenge, getCurrentUserId(), mServiceReceiver, flags); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in enroll: ", e); + if (callback != null) { + // Though this may not be a hardware issue, it will cause apps to give up or try + // again later. + callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE, + getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE)); + } + } + } + + /** + * Requests a pre-enrollment auth token to tie enrollment to the confirmation of + * existing device credentials (e.g. pin/pattern/password). + * @hide + */ + public long preEnroll() { + long result = 0; + if (mService != null) try { + result = mService.preEnroll(mToken); } catch (RemoteException e) { - Log.v(TAG, "Remote exception in enroll: ", e); - stopListening(); + Log.w(TAG, "Remote exception in enroll: ", e); } + return result; } /** * Remove given fingerprint template from fingerprint hardware and/or protected storage. * @param fp the fingerprint item to remove * @param callback an optional callback to verify that fingerprint templates have been - * successfully removed. May be null of no callback is required. + * successfully removed. May be null of no callback is required. + * * @hide */ public void remove(Fingerprint fp, RemovalCallback callback) { if (mService != null) try { mRemovalCallback = callback; mRemovalFingerprint = fp; - startListening(); - mService.remove(mToken, fp.getFingerId(), getCurrentUserId()); + mService.remove(mToken, fp.getFingerId(), getCurrentUserId(), mServiceReceiver); } catch (RemoteException e) { - Log.v(TAG, "Remote in remove: ", e); - stopListening(); + Log.w(TAG, "Remote exception in remove: ", e); + if (callback != null) { + callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE, + getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE)); + } } } @@ -302,6 +494,7 @@ public class FingerprintManager { * Renames the given fingerprint template * @param fpId the fingerprint id * @param newName the new name + * * @hide */ public void rename(int fpId, String newName) { @@ -320,6 +513,8 @@ public class FingerprintManager { /** * Obtain the list of enrolled fingerprints templates. * @return list of current fingerprint items + * + * @hide */ public List<Fingerprint> getEnrolledFingerprints() { if (mService != null) try { @@ -333,6 +528,7 @@ public class FingerprintManager { /** * Determine if fingerprint hardware is present and functional. * @return true if hardware is present and functional, false otherwise. + * * @hide */ public boolean isHardwareDetected() { @@ -403,8 +599,13 @@ public class FingerprintManager { private void sendProcessedResult(Fingerprint fp) { if (mAuthenticationCallback != null) { - AuthenticationResult result = new AuthenticationResult(mCryptoObject, fp); - mAuthenticationCallback.onAuthenticationSucceeded(result); + if (fp.getFingerId() == 0 && fp.getGroupId() == 0) { + // Fingerprint template valid but doesn't match one in database + mAuthenticationCallback.onAuthenticationFailed(); + } else { + final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fp); + mAuthenticationCallback.onAuthenticationSucceeded(result); + } } } @@ -418,65 +619,6 @@ public class FingerprintManager { mAuthenticationCallback.onAuthenticationHelp(acquireInfo, msg); } } - - private String getErrorString(int errMsg) { - switch (errMsg) { - case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: - return mContext.getString( - com.android.internal.R.string.fingerprint_error_unable_to_process); - case FINGERPRINT_ERROR_HW_UNAVAILABLE: - return mContext.getString( - com.android.internal.R.string.fingerprint_error_hw_not_available); - case FINGERPRINT_ERROR_NO_SPACE: - return mContext.getString( - com.android.internal.R.string.fingerprint_error_no_space); - case FINGERPRINT_ERROR_TIMEOUT: - return mContext.getString( - com.android.internal.R.string.fingerprint_error_timeout); - default: - if (errMsg >= FINGERPRINT_ERROR_VENDOR_BASE) { - int msgNumber = errMsg - FINGERPRINT_ERROR_VENDOR_BASE; - String[] msgArray = mContext.getResources().getStringArray( - com.android.internal.R.array.fingerprint_error_vendor); - if (msgNumber < msgArray.length) { - return msgArray[msgNumber]; - } - } - return null; - } - } - - private String getAcquiredString(int acquireInfo) { - switch (acquireInfo) { - case FINGERPRINT_ACQUIRED_GOOD: - return null; - case FINGERPRINT_ACQUIRED_PARTIAL: - return mContext.getString( - com.android.internal.R.string.fingerprint_acquired_partial); - case FINGERPRINT_ACQUIRED_INSUFFICIENT: - return mContext.getString( - com.android.internal.R.string.fingerprint_acquired_insufficient); - case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: - return mContext.getString( - com.android.internal.R.string.fingerprint_acquired_imager_dirty); - case FINGERPRINT_ACQUIRED_TOO_SLOW: - return mContext.getString( - com.android.internal.R.string.fingerprint_acquired_too_slow); - case FINGERPRINT_ACQUIRED_TOO_FAST: - return mContext.getString( - com.android.internal.R.string.fingerprint_acquired_too_fast); - default: - if (acquireInfo >= FINGERPRINT_ACQUIRED_VENDOR_BASE) { - int msgNumber = acquireInfo - FINGERPRINT_ACQUIRED_VENDOR_BASE; - String[] msgArray = mContext.getResources().getStringArray( - com.android.internal.R.array.fingerprint_acquired_vendor); - if (msgNumber < msgArray.length) { - return msgArray[msgNumber]; - } - } - return null; - } - } }; /** @@ -499,39 +641,84 @@ public class FingerprintManager { } } - /** - * Stops the client from listening to fingerprint events. - */ - private void stopListening() { - if (mService != null) { - try { - if (mListening) { - mService.removeListener(mToken, mServiceReceiver); - mListening = false; + private void clearCallbacks() { + mAuthenticationCallback = null; + mEnrollmentCallback = null; + mRemovalCallback = null; + } + + private void cancelEnrollment(long challenge) { + if (mService != null) try { + mService.cancelEnrollment(mToken); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Remote exception while canceling enrollment"); + } + } + + private void cancelAuthentication(CryptoObject cryptoObject) { + if (mService != null) try { + mService.cancelAuthentication(mToken); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Remote exception while canceling enrollment"); + } + } + + private String getErrorString(int errMsg) { + switch (errMsg) { + case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_unable_to_process); + case FINGERPRINT_ERROR_HW_UNAVAILABLE: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_hw_not_available); + case FINGERPRINT_ERROR_NO_SPACE: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_no_space); + case FINGERPRINT_ERROR_TIMEOUT: + return mContext.getString( + com.android.internal.R.string.fingerprint_error_timeout); + default: + if (errMsg >= FINGERPRINT_ERROR_VENDOR_BASE) { + int msgNumber = errMsg - FINGERPRINT_ERROR_VENDOR_BASE; + String[] msgArray = mContext.getResources().getStringArray( + com.android.internal.R.array.fingerprint_error_vendor); + if (msgNumber < msgArray.length) { + return msgArray[msgNumber]; + } } - } catch (RemoteException e) { - Log.v(TAG, "Remote exception in stopListening(): ", e); - } - } else { - Log.w(TAG, "stopListening(): Service not connected!"); + return null; } } - /** - * Starts listening for fingerprint events for this client. - */ - private void startListening() { - if (mService != null) { - try { - if (!mListening) { - mService.addListener(mToken, mServiceReceiver, getCurrentUserId()); - mListening = true; + private String getAcquiredString(int acquireInfo) { + switch (acquireInfo) { + case FINGERPRINT_ACQUIRED_GOOD: + return null; + case FINGERPRINT_ACQUIRED_PARTIAL: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_partial); + case FINGERPRINT_ACQUIRED_INSUFFICIENT: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_insufficient); + case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_imager_dirty); + case FINGERPRINT_ACQUIRED_TOO_SLOW: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_too_slow); + case FINGERPRINT_ACQUIRED_TOO_FAST: + return mContext.getString( + com.android.internal.R.string.fingerprint_acquired_too_fast); + default: + if (acquireInfo >= FINGERPRINT_ACQUIRED_VENDOR_BASE) { + int msgNumber = acquireInfo - FINGERPRINT_ACQUIRED_VENDOR_BASE; + String[] msgArray = mContext.getResources().getStringArray( + com.android.internal.R.array.fingerprint_acquired_vendor); + if (msgNumber < msgArray.length) { + return msgArray[msgNumber]; + } } - } catch (RemoteException e) { - Log.v(TAG, "Remote exception in startListening(): ", e); - } - } else { - Log.w(TAG, "startListening(): Service not connected!"); + return null; } } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index c5a45e2..2fcb20e 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -26,13 +26,21 @@ import java.util.List; */ interface IFingerprintService { // Authenticate the given sessionId with a fingerprint - void authenticate(IBinder token, long sessionId, int groupId, int flags); + void authenticate(IBinder token, long sessionId, int groupId, + IFingerprintServiceReceiver receiver, int flags); + + // Cancel authentication for the given sessionId + void cancelAuthentication(IBinder token); // Start fingerprint enrollment - void enroll(IBinder token, int groupId, int flags); + void enroll(IBinder token, long challenge, int groupId, IFingerprintServiceReceiver receiver, + int flags); + + // Cancel enrollment in progress + void cancelEnrollment(IBinder token); // Any errors resulting from this call will be returned to the listener - void remove(IBinder token, int fingerId, int groupId); + void remove(IBinder token, int fingerId, int groupId, IFingerprintServiceReceiver receiver); // Rename the fingerprint specified by fingerId and groupId to the given name void rename(int fingerId, int groupId, String name); @@ -40,15 +48,12 @@ interface IFingerprintService { // Get a list of enrolled fingerprints in the given group. List<Fingerprint> getEnrolledFingerprints(int groupId); - // Register listener for an instance of FingerprintManager - void addListener(IBinder token, IFingerprintServiceReceiver receiver, int userId); - - // Unregister listener for an instance of FingerprintManager - void removeListener(IBinder token, IFingerprintServiceReceiver receiver); - // Determine if HAL is loaded and ready boolean isHardwareDetected(long deviceId); + // Get a pre-enrollment authentication token + long preEnroll(IBinder token); + // Gets the number of hardware devices // int getHardwareDeviceCount(); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 3abccbc..da2c5e0 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -589,9 +589,9 @@ public class ConnectivityManager { * network. * * @return a {@link NetworkInfo} object for the current default network - * or {@code null} if no network default network is currently active + * or {@code null} if no default network is currently active * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. */ public NetworkInfo getActiveNetworkInfo() { @@ -738,9 +738,9 @@ public class ConnectivityManager { * network. * * @return a {@link NetworkInfo} object for the current default network - * or {@code null} if no network default network is currently active + * or {@code null} if no default network is currently active * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * * {@hide} @@ -760,7 +760,7 @@ public class ConnectivityManager { * for the current default network, or {@code null} if there * is no current default network. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -780,7 +780,7 @@ public class ConnectivityManager { * for the given networkType, or {@code null} if there is * no current default network. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -830,7 +830,7 @@ public class ConnectivityManager { * @return a boolean, {@code true} indicating success. All network types * will be tried, even if some fail. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. * {@hide} */ @@ -851,7 +851,7 @@ public class ConnectivityManager { * {@code} false to turn it off. * @return a boolean, {@code true} indicating success. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. * {@hide} */ @@ -1202,7 +1202,7 @@ public class ConnectivityManager { * @return {@code true} on success, {@code false} on failure * * @deprecated Deprecated in favor of the {@link #requestNetwork}, - * {@link #setProcessDefaultNetwork} and {@link Network#getSocketFactory} api. + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} api. */ public boolean requestRouteToHost(int networkType, int hostAddress) { return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress)); @@ -1220,7 +1220,7 @@ public class ConnectivityManager { * @return {@code true} on success, {@code false} on failure * @hide * @deprecated Deprecated in favor of the {@link #requestNetwork} and - * {@link #setProcessDefaultNetwork} api. + * {@link #bindProcessToNetwork} api. */ public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { try { @@ -1273,7 +1273,7 @@ public class ConnectivityManager { * network is active. Quota status can change rapidly, so these values * shouldn't be cached. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * * @hide @@ -1345,7 +1345,7 @@ public class ConnectivityManager { * listener. * <p> * If the process default network has been set with - * {@link ConnectivityManager#setProcessDefaultNetwork} this function will not + * {@link ConnectivityManager#bindProcessToNetwork} this function will not * reflect the process's default, but the system default. * * @param l The listener to be told when the network is active. @@ -1430,11 +1430,20 @@ public class ConnectivityManager { * situations where a Context pointer is unavailable. * @hide */ - public static ConnectivityManager getInstance() { - if (sInstance == null) { + static ConnectivityManager getInstanceOrNull() { + return sInstance; + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + private static ConnectivityManager getInstance() { + if (getInstanceOrNull() == null) { throw new IllegalStateException("No ConnectivityManager yet constructed"); } - return sInstance; + return getInstanceOrNull(); } /** @@ -1443,7 +1452,7 @@ public class ConnectivityManager { * * @return an array of 0 or more Strings of tetherable interface names. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1460,7 +1469,7 @@ public class ConnectivityManager { * * @return an array of 0 or more String of currently tethered interface names. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1483,7 +1492,7 @@ public class ConnectivityManager { * @return an array of 0 or more String indicating the interface names * which failed to tether. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1521,7 +1530,7 @@ public class ConnectivityManager { * @param iface the interface name to tether. * @return error a {@code TETHER_ERROR} value indicating success or failure type * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. * {@hide} */ @@ -1539,7 +1548,7 @@ public class ConnectivityManager { * @param iface the interface name to untether. * @return error a {@code TETHER_ERROR} value indicating success or failure type * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. * {@hide} */ @@ -1558,7 +1567,7 @@ public class ConnectivityManager { * * @return a boolean - {@code true} indicating Tethering is supported. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1578,7 +1587,7 @@ public class ConnectivityManager { * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable usb interfaces. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1598,7 +1607,7 @@ public class ConnectivityManager { * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable wifi interfaces. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1618,7 +1627,7 @@ public class ConnectivityManager { * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable bluetooth interfaces. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1640,7 +1649,7 @@ public class ConnectivityManager { * @param enable a boolean - {@code true} to enable tethering * @return error a {@code TETHER_ERROR} value indicating success or failure type * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. * {@hide} */ @@ -1683,7 +1692,7 @@ public class ConnectivityManager { * @return error The error code of the last error tethering or untethering the named * interface * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ @@ -1702,7 +1711,7 @@ public class ConnectivityManager { * @param networkType The type of network you want to report on * @param percentage The quality of the connection 0 is bad, 100 is good * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#STATUS_BAR}. * {@hide} */ @@ -1738,7 +1747,7 @@ public class ConnectivityManager { * @param p The a {@link ProxyInfo} object defining the new global * HTTP proxy. A {@code null} value will clear the global HTTP proxy. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * android.Manifest.permission#CONNECTIVITY_INTERNAL. * @hide */ @@ -1755,7 +1764,7 @@ public class ConnectivityManager { * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null} * if no global HTTP proxy is set. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * @hide */ @@ -1770,15 +1779,14 @@ public class ConnectivityManager { /** * Get the current default HTTP proxy settings. If a global proxy is set it will be returned, * otherwise if this process is bound to a {@link Network} using - * {@link #setProcessDefaultNetwork} then that {@code Network}'s proxy is returned, otherwise + * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise * the default network's proxy is returned. * * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no * HTTP proxy is active. - * @hide */ public ProxyInfo getDefaultProxy() { - final Network network = getProcessDefaultNetwork(); + final Network network = getBoundNetworkForProcess(); if (network != null) { final ProxyInfo globalProxy = getGlobalProxy(); if (globalProxy != null) return globalProxy; @@ -1804,7 +1812,7 @@ public class ConnectivityManager { * @param networkType The network type we'd like to check * @return {@code true} if supported, else {@code false} * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * @hide */ @@ -1826,7 +1834,7 @@ public class ConnectivityManager { * @return {@code true} if large transfers should be avoided, otherwise * {@code false}. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. */ public boolean isActiveNetworkMetered() { @@ -1862,7 +1870,7 @@ public class ConnectivityManager { * in question. * @param isCaptivePortal true/false. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. * {@hide} */ @@ -1937,7 +1945,7 @@ public class ConnectivityManager { * * @param enable whether to enable airplane mode or not * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. * @hide */ @@ -2338,9 +2346,8 @@ public class ConnectivityManager { * successfully finding a network for the applications request. Retrieve it with * {@link android.content.Intent#getParcelableExtra(String)}. * <p> - * Note that if you intend to invoke {@link #setProcessDefaultNetwork} or - * {@link Network#openConnection(java.net.URL)} then you must get a - * ConnectivityManager instance before doing so. + * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)} + * then you must get a ConnectivityManager instance before doing so. */ public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; @@ -2491,15 +2498,42 @@ public class ConnectivityManager { * Sockets created by Network.getSocketFactory().createSocket() and * performing network-specific host name resolutions via * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code bindProcessToNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean bindProcessToNetwork(Network network) { + // Forcing callers to call thru non-static function ensures ConnectivityManager + // instantiated. + return setProcessDefaultNetwork(network); + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling * {@code setProcessDefaultNetwork}. * * @param network The {@link Network} to bind the current process to, or {@code null} to clear * the current binding. * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @deprecated This function can throw {@link IllegalStateException}. Use + * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork} + * is a direct replacement. */ public static boolean setProcessDefaultNetwork(Network network) { int netId = (network == null) ? NETID_UNSET : network.netId; - if (netId == NetworkUtils.getNetworkBoundToProcess()) { + if (netId == NetworkUtils.getBoundNetworkForProcess()) { return true; } if (NetworkUtils.bindProcessToNetwork(netId)) { @@ -2519,19 +2553,34 @@ public class ConnectivityManager { /** * Returns the {@link Network} currently bound to this process via - * {@link #setProcessDefaultNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + */ + public Network getBoundNetworkForProcess() { + // Forcing callers to call thru non-static function ensures ConnectivityManager + // instantiated. + return getProcessDefaultNetwork(); + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. * * @return {@code Network} to which this process is bound, or {@code null}. + * @deprecated Using this function can lead to other functions throwing + * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead. + * {@code getBoundNetworkForProcess} is a direct replacement. */ public static Network getProcessDefaultNetwork() { - int netId = NetworkUtils.getNetworkBoundToProcess(); + int netId = NetworkUtils.getBoundNetworkForProcess(); if (netId == NETID_UNSET) return null; return new Network(netId); } /** * Binds host resolutions performed by this process to {@code network}. - * {@link #setProcessDefaultNetwork} takes precedence over this setting. + * {@link #bindProcessToNetwork} takes precedence over this setting. * * @param network The {@link Network} to bind host resolutions from the current process to, or * {@code null} to clear the current binding. diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index 1b8adc8..73ef78e 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -37,7 +37,7 @@ import android.util.Log; * StateMachine that interacts with the native DHCP client and can talk to * a controller that also needs to be a StateMachine * - * The Dhcp state machine provides the following features: + * The DhcpStateMachine provides the following features: * - Wakeup and renewal using the native DHCP client (which will not renew * on its own when the device is in suspend state and this can lead to device * holding IP address beyond expiry) @@ -72,11 +72,6 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { //Used for sanity check on setting up renewal private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; // 5 minutes - private enum DhcpAction { - START, - RENEW - }; - private final String mInterfaceName; private boolean mRegisteredForPreDhcpNotification = false; @@ -99,6 +94,9 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { * after pre DHCP action is complete */ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7; + /* Command from ourselves to see if DHCP results are available */ + private static final int CMD_GET_DHCP_RESULTS = BASE + 8; + /* Message.arg1 arguments to CMD_POST_DHCP notification */ public static final int DHCP_SUCCESS = 1; public static final int DHCP_FAILURE = 2; @@ -108,6 +106,7 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { private State mWaitBeforeStartState = new WaitBeforeStartState(); private State mRunningState = new RunningState(); private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(); + private State mPollingState = new PollingState(); private DhcpStateMachine(Context context, StateMachine controller, String intf) { super(TAG); @@ -139,6 +138,7 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { addState(mDefaultState); addState(mStoppedState, mDefaultState); addState(mWaitBeforeStartState, mDefaultState); + addState(mPollingState, mDefaultState); addState(mRunningState, mDefaultState); addState(mWaitBeforeRenewalState, mDefaultState); @@ -206,6 +206,10 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); + if (!NetworkUtils.stopDhcp(mInterfaceName)) { + Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName); + } + mDhcpResults = null; } @Override @@ -219,7 +223,7 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { mController.sendMessage(CMD_PRE_DHCP_ACTION); transitionTo(mWaitBeforeStartState); } else { - if (runDhcp(DhcpAction.START)) { + if (runDhcpStart()) { transitionTo(mRunningState); } } @@ -247,10 +251,10 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { case CMD_PRE_DHCP_ACTION_COMPLETE: - if (runDhcp(DhcpAction.START)) { + if (runDhcpStart()) { transitionTo(mRunningState); } else { - transitionTo(mStoppedState); + transitionTo(mPollingState); } break; case CMD_STOP_DHCP: @@ -267,6 +271,55 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { } } + class PollingState extends State { + private static final long MAX_DELAY_SECONDS = 32; + private long delaySeconds; + + private void scheduleNextResultsCheck() { + sendMessageDelayed(obtainMessage(CMD_GET_DHCP_RESULTS), delaySeconds * 1000); + delaySeconds *= 2; + if (delaySeconds > MAX_DELAY_SECONDS) { + delaySeconds = MAX_DELAY_SECONDS; + } + } + + @Override + public void enter() { + if (DBG) Log.d(TAG, "Entering " + getName() + "\n"); + delaySeconds = 1; + scheduleNextResultsCheck(); + } + + @Override + public boolean processMessage(Message message) { + boolean retValue = HANDLED; + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_GET_DHCP_RESULTS: + if (DBG) Log.d(TAG, "GET_DHCP_RESULTS on " + mInterfaceName); + if (dhcpSucceeded()) { + transitionTo(mRunningState); + } else { + scheduleNextResultsCheck(); + } + break; + case CMD_STOP_DHCP: + transitionTo(mStoppedState); + break; + default: + retValue = NOT_HANDLED; + break; + } + return retValue; + } + + @Override + public void exit() { + if (DBG) Log.d(TAG, "Exiting " + getName() + "\n"); + removeMessages(CMD_GET_DHCP_RESULTS); + } + } + class RunningState extends State { @Override public void enter() { @@ -280,9 +333,6 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { switch (message.what) { case CMD_STOP_DHCP: mAlarmManager.cancel(mDhcpRenewalIntent); - if (!NetworkUtils.stopDhcp(mInterfaceName)) { - Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName); - } transitionTo(mStoppedState); break; case CMD_RENEW_DHCP: @@ -292,7 +342,7 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { transitionTo(mWaitBeforeRenewalState); //mDhcpRenewWakeLock is released in WaitBeforeRenewalState } else { - if (!runDhcp(DhcpAction.RENEW)) { + if (!runDhcpRenew()) { transitionTo(mStoppedState); } mDhcpRenewWakeLock.release(); @@ -321,13 +371,10 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { switch (message.what) { case CMD_STOP_DHCP: mAlarmManager.cancel(mDhcpRenewalIntent); - if (!NetworkUtils.stopDhcp(mInterfaceName)) { - Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName); - } transitionTo(mStoppedState); break; case CMD_PRE_DHCP_ACTION_COMPLETE: - if (runDhcp(DhcpAction.RENEW)) { + if (runDhcpRenew()) { transitionTo(mRunningState); } else { transitionTo(mStoppedState); @@ -348,52 +395,68 @@ public class DhcpStateMachine extends BaseDhcpStateMachine { } } - private boolean runDhcp(DhcpAction dhcpAction) { - boolean success = false; + private boolean dhcpSucceeded() { DhcpResults dhcpResults = new DhcpResults(); - - if (dhcpAction == DhcpAction.START) { - /* Stop any existing DHCP daemon before starting new */ - NetworkUtils.stopDhcp(mInterfaceName); - if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName); - success = NetworkUtils.runDhcp(mInterfaceName, dhcpResults); - } else if (dhcpAction == DhcpAction.RENEW) { - if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName); - success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults); - if (success) dhcpResults.updateFromDhcpRequest(mDhcpResults); + if (!NetworkUtils.getDhcpResults(mInterfaceName, dhcpResults)) { + return false; } - if (success) { - if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName); - long leaseDuration = dhcpResults.leaseDuration; //int to long conversion - - //Sanity check for renewal - if (leaseDuration >= 0) { - //TODO: would be good to notify the user that his network configuration is - //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS - if (leaseDuration < MIN_RENEWAL_TIME_SECS) { - leaseDuration = MIN_RENEWAL_TIME_SECS; - } - //Do it a bit earlier than half the lease duration time - //to beat the native DHCP client and avoid extra packets - //48% for one hour lease time = 29 minutes - mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + - leaseDuration * 480, //in milliseconds - mDhcpRenewalIntent); - } else { - //infinite lease time, no renewal needed - } - mDhcpResults = dhcpResults; - mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults) - .sendToTarget(); + if (DBG) Log.d(TAG, "DHCP results found for " + mInterfaceName); + long leaseDuration = dhcpResults.leaseDuration; //int to long conversion + + //Sanity check for renewal + if (leaseDuration >= 0) { + //TODO: would be good to notify the user that his network configuration is + //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS + if (leaseDuration < MIN_RENEWAL_TIME_SECS) { + leaseDuration = MIN_RENEWAL_TIME_SECS; + } + //Do it a bit earlier than half the lease duration time + //to beat the native DHCP client and avoid extra packets + //48% for one hour lease time = 29 minutes + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + + leaseDuration * 480, //in milliseconds + mDhcpRenewalIntent); } else { - Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " + + //infinite lease time, no renewal needed + } + + // Fill in any missing fields in dhcpResults from the previous results. + // If mDhcpResults is null (i.e. this is the first server response), + // this is a noop. + dhcpResults.updateFromDhcpRequest(mDhcpResults); + mDhcpResults = dhcpResults; + mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults) + .sendToTarget(); + return true; + } + + private boolean runDhcpStart() { + /* Stop any existing DHCP daemon before starting new */ + NetworkUtils.stopDhcp(mInterfaceName); + mDhcpResults = null; + + if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName); + if (!NetworkUtils.startDhcp(mInterfaceName) || !dhcpSucceeded()) { + Log.e(TAG, "DHCP request failed on " + mInterfaceName + ": " + + NetworkUtils.getDhcpError()); + mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0) + .sendToTarget(); + return false; + } + return true; + } + + private boolean runDhcpRenew() { + if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName); + if (!NetworkUtils.startDhcpRenew(mInterfaceName) || !dhcpSucceeded()) { + Log.e(TAG, "DHCP renew failed on " + mInterfaceName + ": " + NetworkUtils.getDhcpError()); - NetworkUtils.stopDhcp(mInterfaceName); mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0) - .sendToTarget(); + .sendToTarget(); + return false; } - return success; + return true; } } diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index ab57c9b..65d325a1 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -48,7 +48,7 @@ import com.android.okhttp.internal.Internal; * {@link ConnectivityManager#registerNetworkCallback} calls. * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis * through a targeted {@link SocketFactory} or process-wide via - * {@link ConnectivityManager#setProcessDefaultNetwork}. + * {@link ConnectivityManager#bindProcessToNetwork}. */ public class Network implements Parcelable { @@ -242,7 +242,10 @@ public class Network implements Parcelable { * @see java.net.URL#openConnection() */ public URLConnection openConnection(URL url) throws IOException { - final ConnectivityManager cm = ConnectivityManager.getInstance(); + final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull(); + if (cm == null) { + throw new IOException("No ConnectivityManager yet constructed, please construct one"); + } // TODO: Should this be optimized to avoid fetching the global proxy for every request? ProxyInfo proxyInfo = cm.getGlobalProxy(); if (proxyInfo == null) { @@ -269,7 +272,6 @@ public class Network implements Parcelable { * @throws IllegalArgumentException if the argument proxy is null. * @throws IOException if an error occurs while opening the connection. * @see java.net.URL#openConnection() - * @hide */ public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException { if (proxy == null) throw new IllegalArgumentException("proxy is null"); @@ -299,7 +301,7 @@ public class Network implements Parcelable { /** * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the * socket will be sent on this {@code Network}, irrespective of any process-wide network binding - * set by {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be + * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be * connected. */ public void bindSocket(DatagramSocket socket) throws IOException { @@ -316,7 +318,7 @@ public class Network implements Parcelable { /** * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket * will be sent on this {@code Network}, irrespective of any process-wide network binding set by - * {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected. + * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. */ public void bindSocket(Socket socket) throws IOException { // Apparently, the kernel doesn't update a connected TCP socket's routing upon mark changes. diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 02fbe73..29dd8ad 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -159,7 +159,7 @@ public class NetworkUtils { * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}. */ - public native static int getNetworkBoundToProcess(); + public native static int getBoundNetworkForProcess(); /** * Binds host resolutions performed by this process to the network designated by {@code netId}. diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index 17a84a7..7172c09 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -40,14 +40,9 @@ public final class Proxy { private static final ProxySelector sDefaultProxySelector; /** - * Used to notify an app that's caching the default connection proxy - * that either the default connection or its proxy has changed. - * The intent will have the following extra value:</p> - * <ul> - * <li><em>EXTRA_PROXY_INFO</em> - The ProxyProperties for the proxy. Non-null, - * though if the proxy is undefined the host string - * will be empty. - * </ul> + * Used to notify an app that's caching the proxy that either the default + * connection has changed or any connection's proxy has changed. The new + * proxy should be queried using {@link ConnectivityManager#getDefaultProxy()}. * * <p class="note">This is a protected intent that can only be sent by the system */ @@ -56,6 +51,11 @@ public final class Proxy { /** * Intent extra included with {@link #PROXY_CHANGE_ACTION} intents. * It describes the new proxy being used (as a {@link ProxyInfo} object). + * @deprecated Because {@code PROXY_CHANGE_ACTION} is sent whenever the proxy + * for any network on the system changes, applications should always use + * {@link ConnectivityManager#getDefaultProxy()} or + * {@link ConnectivityManager#getLinkProperties(Network)}.{@link LinkProperties#getHttpProxy()} + * to get the proxy for the Network(s) they are using. */ public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index c26af06..a0e65eb 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -679,7 +679,7 @@ public class VpnService extends Service { * * By default, all traffic from apps is forwarded through the VPN interface and it is not * possible for apps to side-step the VPN. If this method is called, apps may use methods - * such as {@link ConnectivityManager#setProcessDefaultNetwork} to instead send/receive + * such as {@link ConnectivityManager#bindProcessToNetwork} to instead send/receive * directly over the underlying network or any other network they have permissions for. * * @return this {@link Builder} object to facilitate chaining of method calls. diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 1566985..c7edb1a 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1947,6 +1947,13 @@ public abstract class BatteryStats implements Parcelable { public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1; /** + * Returns true if the BatteryStats object has detailed bluetooth power reports. + * When true, calling {@link #getBluetoothControllerActivity(int, int)} will yield the + * actual power data. + */ + public abstract boolean hasBluetoothActivityReporting(); + + /** * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the * respective state. @@ -1956,6 +1963,13 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBluetoothControllerActivity(int type, int which); /** + * Returns true if the BatteryStats object has detailed WiFi power reports. + * When true, calling {@link #getWifiControllerActivity(int, int)} will yield the + * actual power data. + */ + public abstract boolean hasWifiActivityReporting(); + + /** * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the * respective state. diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IHardwareService.aidl deleted file mode 100644 index 38abfc0..0000000 --- a/core/java/android/os/IHardwareService.aidl +++ /dev/null @@ -1,26 +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.os; - -/** {@hide} */ -interface IHardwareService -{ - // obsolete flashlight support - boolean getFlashlightEnabled(); - void setFlashlightEnabled(boolean on); -} - diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 236003b..c2fd3c3 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -38,6 +38,7 @@ interface IUserManager { List<UserInfo> getProfiles(int userHandle, boolean enabledOnly); UserInfo getProfileParent(int userHandle); UserInfo getUserInfo(int userHandle); + long getUserCreationTime(int userHandle); boolean isRestricted(); int getUserSerialNumber(int userHandle); int getUserHandle(int userSerialNumber); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 3601a1c..b9e307f 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1320,4 +1320,19 @@ public class UserManager { } return new Bundle(); } + + /** + * Returns creation time of the user or of a managed profile associated with the calling user. + * @param userHandle user handle of the user or a managed profile associated with the + * calling user. + * @return creation time in milliseconds since Epoch time. + */ + public long getUserCreationTime(UserHandle userHandle) { + try { + return mService.getUserCreationTime(userHandle.getIdentifier()); + } catch (RemoteException re) { + Log.w(TAG, "Could not get user creation time", re); + return 0; + } + } } diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java index 56c55f1..4704b67 100644 --- a/core/java/android/os/storage/DiskInfo.java +++ b/core/java/android/os/storage/DiskInfo.java @@ -16,7 +16,7 @@ package android.os.storage; -import android.content.Context; +import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import android.util.DebugUtils; @@ -57,12 +57,12 @@ public class DiskInfo implements Parcelable { volumes = parcel.readStringArray(); } - public String getDescription(Context context) { + public String getDescription() { // TODO: splice vendor label into these strings if ((flags & FLAG_SD) != 0) { - return context.getString(com.android.internal.R.string.storage_sd_card); + return Resources.getSystem().getString(com.android.internal.R.string.storage_sd_card); } else if ((flags & FLAG_USB) != 0) { - return context.getString(com.android.internal.R.string.storage_usb); + return Resources.getSystem().getString(com.android.internal.R.string.storage_usb); } else { return null; } @@ -119,7 +119,7 @@ public class DiskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(id); - parcel.writeInt(flags); + parcel.writeInt(this.flags); parcel.writeLong(size); parcel.writeString(label); parcel.writeStringArray(volumes); diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 5a5d8b7..bd42f6a 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -30,10 +30,12 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import com.android.internal.os.SomeArgs; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import java.io.File; @@ -454,6 +456,18 @@ public class StorageManager { } /** {@hide} */ + public @Nullable DiskInfo findDiskByVolumeId(String volId) { + Preconditions.checkNotNull(volId); + // TODO; go directly to service to make this faster + for (DiskInfo disk : getDisks()) { + if (ArrayUtils.contains(disk.volumes, volId)) { + return disk; + } + } + return null; + } + + /** {@hide} */ public @Nullable VolumeInfo findVolumeById(String id) { Preconditions.checkNotNull(id); // TODO; go directly to service to make this faster @@ -487,6 +501,23 @@ public class StorageManager { } /** {@hide} */ + public @Nullable String getBestVolumeDescription(String volId) { + String descrip = null; + + final VolumeInfo vol = findVolumeById(volId); + if (vol != null) { + descrip = vol.getDescription(); + } + + final DiskInfo disk = findDiskByVolumeId(volId); + if (disk != null && TextUtils.isEmpty(descrip)) { + descrip = disk.getDescription(); + } + + return descrip; + } + + /** {@hide} */ public void mount(String volId) { try { mMountService.mount(volId); diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 2dc0361..beca8b8 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.mtp.MtpStorage; import android.os.Environment; import android.os.Parcel; @@ -44,6 +45,8 @@ import java.io.File; * @hide */ public class VolumeInfo implements Parcelable { + /** Stub volume representing internal private storage */ + public static final String ID_PRIVATE_INTERNAL = "private"; /** Real volume representing internal emulated storage */ public static final String ID_EMULATED_INTERNAL = "emulated"; @@ -59,6 +62,7 @@ public class VolumeInfo implements Parcelable { public static final int STATE_FORMATTING = 3; public static final int STATE_UNMOUNTING = 4; public static final int STATE_UNMOUNTABLE = 5; + public static final int STATE_REMOVED = 6; public static final int FLAG_PRIMARY = 1 << 0; public static final int FLAG_VISIBLE = 1 << 1; @@ -73,12 +77,14 @@ public class VolumeInfo implements Parcelable { sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED); sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING); sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE); + sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED); sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED); sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING); sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED); sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT); sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE); + sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED); } /** vold state */ @@ -96,8 +102,6 @@ public class VolumeInfo implements Parcelable { public final int mtpIndex; public String nickname; - public DiskInfo disk; - public VolumeInfo(String id, int type, int mtpIndex) { this.id = Preconditions.checkNotNull(id); this.type = type; @@ -135,9 +139,9 @@ public class VolumeInfo implements Parcelable { return getBroadcastForEnvironment(getEnvironmentForState(state)); } - public String getDescription(Context context) { - if (ID_EMULATED_INTERNAL.equals(id)) { - return context.getString(com.android.internal.R.string.storage_internal); + public @Nullable String getDescription() { + if (ID_PRIVATE_INTERNAL.equals(id)) { + return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); } else if (!TextUtils.isEmpty(nickname)) { return nickname; } else if (!TextUtils.isEmpty(fsLabel)) { @@ -189,7 +193,7 @@ public class VolumeInfo implements Parcelable { userPath = new File("/dev/null"); } - String description = getDescription(context); + String description = getDescription(); if (description == null) { description = context.getString(android.R.string.unknownName); } @@ -283,7 +287,7 @@ public class VolumeInfo implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(id); parcel.writeInt(type); - parcel.writeInt(flags); + parcel.writeInt(this.flags); parcel.writeInt(userId); parcel.writeInt(state); parcel.writeString(fsType); diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 9a0858a..69338b0 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -106,6 +106,14 @@ public final class DocumentsContract { /** {@hide} */ public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; + /** {@hide} */ + public static final String + ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT"; + + /** {@hide} */ + public static final String + ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; + /** * Buffer is large enough to rewind past any EXIF headers. */ @@ -473,6 +481,15 @@ public final class DocumentsContract { * @hide */ public static final int FLAG_ADVANCED = 1 << 17; + + /** + * Flag indicating that this root has settings. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS + * @hide + */ + public static final int FLAG_HAS_SETTINGS = 1 << 18; } /** diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java index 0f1d422..b3a3aad 100644 --- a/core/java/android/security/keymaster/KeyCharacteristics.java +++ b/core/java/android/security/keymaster/KeyCharacteristics.java @@ -19,6 +19,10 @@ package android.security.keymaster; import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + /** * @hide */ @@ -28,10 +32,12 @@ public class KeyCharacteristics implements Parcelable { public static final Parcelable.Creator<KeyCharacteristics> CREATOR = new Parcelable.Creator<KeyCharacteristics>() { + @Override public KeyCharacteristics createFromParcel(Parcel in) { return new KeyCharacteristics(in); } + @Override public KeyCharacteristics[] newArray(int length) { return new KeyCharacteristics[length]; } @@ -48,6 +54,7 @@ public class KeyCharacteristics implements Parcelable { return 0; } + @Override public void writeToParcel(Parcel out, int flags) { swEnforced.writeToParcel(out, flags); hwEnforced.writeToParcel(out, flags); @@ -57,5 +64,53 @@ public class KeyCharacteristics implements Parcelable { swEnforced = KeymasterArguments.CREATOR.createFromParcel(in); hwEnforced = KeymasterArguments.CREATOR.createFromParcel(in); } + + public Integer getInteger(int tag) { + if (hwEnforced.containsTag(tag)) { + return hwEnforced.getInt(tag, -1); + } else if (swEnforced.containsTag(tag)) { + return swEnforced.getInt(tag, -1); + } else { + return null; + } + } + + public int getInt(int tag, int defaultValue) { + Integer result = getInteger(tag); + return (result != null) ? result : defaultValue; + } + + public List<Integer> getInts(int tag) { + List<Integer> result = new ArrayList<Integer>(); + result.addAll(hwEnforced.getInts(tag)); + result.addAll(swEnforced.getInts(tag)); + return result; + } + + public Date getDate(int tag) { + Date result = hwEnforced.getDate(tag, null); + if (result == null) { + result = swEnforced.getDate(tag, null); + } + return result; + } + + public Date getDate(int tag, Date defaultValue) { + if (hwEnforced.containsTag(tag)) { + return hwEnforced.getDate(tag, null); + } else if (hwEnforced.containsTag(tag)) { + return swEnforced.getDate(tag, null); + } else { + return defaultValue; + } + } + + public boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getBoolean(tag, false); + } else { + return keyCharacteristics.swEnforced.getBoolean(tag, false); + } + } } diff --git a/core/java/android/security/keymaster/KeymasterArguments.java b/core/java/android/security/keymaster/KeymasterArguments.java index b5fd4bd..8ed288c 100644 --- a/core/java/android/security/keymaster/KeymasterArguments.java +++ b/core/java/android/security/keymaster/KeymasterArguments.java @@ -34,9 +34,12 @@ public class KeymasterArguments implements Parcelable { public static final Parcelable.Creator<KeymasterArguments> CREATOR = new Parcelable.Creator<KeymasterArguments>() { + @Override public KeymasterArguments createFromParcel(Parcel in) { return new KeymasterArguments(in); } + + @Override public KeymasterArguments[] newArray(int size) { return new KeymasterArguments[size]; } @@ -54,6 +57,12 @@ public class KeymasterArguments implements Parcelable { mArguments.add(new KeymasterIntArgument(tag, value)); } + public void addInts(int tag, int... values) { + for (int value : values) { + addInt(tag, value); + } + } + public void addBoolean(int tag) { mArguments.add(new KeymasterBooleanArgument(tag)); } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index e16691c..ea53c0d 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -47,12 +47,11 @@ public final class KeymasterDefs { public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1; public static final int KM_TAG_ALGORITHM = KM_ENUM | 2; public static final int KM_TAG_KEY_SIZE = KM_INT | 3; - public static final int KM_TAG_BLOCK_MODE = KM_ENUM | 4; - public static final int KM_TAG_DIGEST = KM_ENUM | 5; - public static final int KM_TAG_MAC_LENGTH = KM_INT | 6; - public static final int KM_TAG_PADDING = KM_ENUM | 7; - public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 8; - public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 9; + public static final int KM_TAG_BLOCK_MODE = KM_ENUM_REP | 4; + public static final int KM_TAG_DIGEST = KM_ENUM_REP | 5; + public static final int KM_TAG_PADDING = KM_ENUM_REP | 6; + public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 7; + public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 8; public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101; public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102; @@ -85,6 +84,7 @@ public final class KeymasterDefs { public static final int KM_TAG_NONCE = KM_BYTES | 1001; public static final int KM_TAG_CHUNK_LENGTH = KM_INT | 1002; public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1003; + public static final int KM_TAG_MAC_LENGTH = KM_INT | 1004; // Algorithm values. public static final int KM_ALGORITHM_RSA = 1; diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 822bfcc..29aaf30 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -41,6 +41,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.PhoneWindow; +import android.view.SearchEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -332,6 +333,12 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override + public boolean onSearchRequested(SearchEvent event) { + return onSearchRequested(); + } + + /** {@inheritDoc} */ + @Override public boolean onSearchRequested() { return false; } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 13fb657..cf29310 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -215,7 +215,7 @@ public class TextToSpeech { * </li> * <li> * A list of feature strings that engines might support, e.g - * {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to + * {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}. These values may be passed in to * {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify * engine behaviour. The engine can be queried for the set of features it supports * through {@link TextToSpeech#getFeatures(java.util.Locale)}. @@ -576,9 +576,9 @@ public class TextToSpeech { * @see TextToSpeech#getFeatures(java.util.Locale) * * @deprecated Starting from API level 21, to select network synthesis, call - * ({@link TextToSpeech#getVoices()}, find a suitable network voice + * {@link TextToSpeech#getVoices()}, find a suitable network voice * ({@link Voice#isNetworkConnectionRequired()}) and pass it - * to {@link TextToSpeech#setVoice(Voice)}). + * to {@link TextToSpeech#setVoice(Voice)}. */ @Deprecated public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts"; diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index 38f4d1c..5af2832 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -1916,7 +1916,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { break; } if (event.isTracking() && !event.isCanceled()) { - launchDefaultSearch(); + launchDefaultSearch(event); } return true; } @@ -4245,14 +4245,19 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * * @return true if search window opened */ - private boolean launchDefaultSearch() { + private boolean launchDefaultSearch(KeyEvent event) { boolean result; final Callback cb = getCallback(); if (cb == null || isDestroyed()) { result = false; } else { sendCloseSystemWindows("search"); - result = cb.onSearchRequested(); + int deviceId = event.getDeviceId(); + SearchEvent searchEvent = null; + if (deviceId != 0) { + searchEvent = new SearchEvent(InputDevice.getDevice(deviceId)); + } + result = cb.onSearchRequested(searchEvent); } if (!result && (getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) { diff --git a/core/java/android/view/SearchEvent.java b/core/java/android/view/SearchEvent.java new file mode 100644 index 0000000..ef51e7d --- /dev/null +++ b/core/java/android/view/SearchEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.InputDevice; + +/** + * Class that contains information about an event that triggers a search. + */ +public class SearchEvent { + + private InputDevice mInputDevice; + + /** @hide */ + public SearchEvent(InputDevice inputDevice) { + mInputDevice = inputDevice; + } + + /** + * Returns the {@link InputDevice} that triggered the search. + * @return InputDevice the InputDevice that triggered the search. + */ + public InputDevice getInputDevice() { + return mInputDevice; + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b6f1e3b..60d2ceb 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.Size; import android.content.ClipData; import android.content.Context; +import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; @@ -3550,6 +3551,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static SparseArray<String> mAttributeMap; /** + * @hide + */ + String mStartActivityRequestWho; + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -4915,6 +4921,58 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Call {@link Context#startActivityForResult(String, Intent, int, Bundle)} for the View's + * Context, creating a unique View identifier to retrieve the result. + * + * @param intent The Intent to be started. + * @param requestCode The request code to use. + * @hide + */ + public void startActivityForResult(Intent intent, int requestCode) { + mStartActivityRequestWho = "@android:view:" + System.identityHashCode(this); + getContext().startActivityForResult(mStartActivityRequestWho, intent, requestCode, null); + } + + /** + * If this View corresponds to the calling who, dispatches the activity result. + * @param who The identifier for the targeted View to receive the result. + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * @return {@code true} if the activity result was dispatched. + * @hide + */ + public boolean dispatchActivityResult( + String who, int requestCode, int resultCode, Intent data) { + if (mStartActivityRequestWho != null && mStartActivityRequestWho.equals(who)) { + onActivityResult(requestCode, resultCode, data); + mStartActivityRequestWho = null; + return true; + } + return false; + } + + /** + * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}. + * + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * @hide + */ + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // Do nothing. + } + + /** * Register a callback to be invoked when a hardware key is pressed in this view. * Key presses in software input methods will generally not trigger the methods of * this listener. @@ -13980,6 +14038,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @CallSuper protected Parcelable onSaveInstanceState() { mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; + if (mStartActivityRequestWho != null) { + BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE); + state.mStartActivityRequestWhoSaved = mStartActivityRequestWho; + return state; + } return BaseSavedState.EMPTY_STATE; } @@ -14039,13 +14102,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @CallSuper protected void onRestoreInstanceState(Parcelable state) { mPrivateFlags |= PFLAG_SAVE_STATE_CALLED; - if (state != BaseSavedState.EMPTY_STATE && state != null) { + if (state != null && !(state instanceof AbsSavedState)) { throw new IllegalArgumentException("Wrong state class, expecting View State but " + "received " + state.getClass().toString() + " instead. This usually happens " + "when two views of different type have the same id in the same hierarchy. " + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure " + "other views do not use the same id."); } + if (state != null && state instanceof BaseSavedState) { + mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved; + } } /** @@ -20735,6 +20801,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * state in {@link android.view.View#onSaveInstanceState()}. */ public static class BaseSavedState extends AbsSavedState { + String mStartActivityRequestWhoSaved; + /** * Constructor used when reading from a parcel. Reads the state of the superclass. * @@ -20742,6 +20810,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public BaseSavedState(Parcel source) { super(source); + mStartActivityRequestWhoSaved = source.readString(); } /** @@ -20753,6 +20822,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, super(superState); } + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeString(mStartActivityRequestWhoSaved); + } + public static final Parcelable.Creator<BaseSavedState> CREATOR = new Parcelable.Creator<BaseSavedState>() { public BaseSavedState createFromParcel(Parcel in) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d0705bb..8d06ce2 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -19,6 +19,7 @@ package android.view; import android.animation.LayoutTransition; import android.annotation.IdRes; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.TypedArray; @@ -814,6 +815,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * @hide + */ + @Override + public boolean dispatchActivityResult( + String who, int requestCode, int resultCode, Intent data) { + if (super.dispatchActivityResult(who, requestCode, resultCode, data)) { + return true; + } + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.dispatchActivityResult(who, requestCode, resultCode, data)) { + return true; + } + } + return false; + } + + /** * Find the nearest view in the specified direction that wants to take * focus. * diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 36f047e..9d0d5ff 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -414,6 +414,15 @@ public abstract class Window { public boolean onSearchRequested(); /** + * Called when the user signals the desire to start a search. + * + * @param searchEvent A {@link SearchEvent} describing the signal to + * start a search. + * @return true if search launched, false if activity refuses (blocks) + */ + public boolean onSearchRequested(SearchEvent searchEvent); + + /** * Called when an action mode is being started for this window. Gives the * callback an opportunity to handle the action mode in its own unique and * beautiful way. If this method returns null the system can choose a way diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java index 979ee95..8ce1f8c 100644 --- a/core/java/android/view/WindowCallbackWrapper.java +++ b/core/java/android/view/WindowCallbackWrapper.java @@ -122,6 +122,11 @@ public class WindowCallbackWrapper implements Window.Callback { } @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return mWrapped.onSearchRequested(searchEvent); + } + + @Override public boolean onSearchRequested() { return mWrapped.onSearchRequested(); } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 4d1209a..0417921 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -16,6 +16,7 @@ package android.view.animation; +import android.content.res.Configuration; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -44,6 +45,16 @@ public class AnimationUtils { private static final int TOGETHER = 0; private static final int SEQUENTIALLY = 1; + private static final float RECOMMENDED_FIELD_OF_VIEW_FOR_TV = 40f; + private static final float ESTIMATED_VIEWING_DISTANCE_FOR_WATCH = 11f; + private static final float AVERAGE_VIEWING_DISTANCE_FOR_PHONES = 14.2f; + private static final float N5_DIAGONAL_VIEW_ANGLE = 19.58f; + private static final float N5_DENSITY = 3.0f; + private static final float N5_DPI = 443f; + + private static final float COTANGENT_OF_HALF_TV_ANGLE = (float) (1 / Math.tan(Math.toRadians + (RECOMMENDED_FIELD_OF_VIEW_FOR_TV / 2))); + /** * Returns the current animation time in milliseconds. This time should be used when invoking @@ -367,4 +378,78 @@ public class AnimationUtils { } return interpolator; } + + /** + * Derives the viewing distance of a device based on the device size (in inches), and the + * device type. + * @hide + */ + public static float getViewingDistance(float width, float height, int uiMode) { + if (uiMode == Configuration.UI_MODE_TYPE_TELEVISION) { + // TV + return (width / 2) * COTANGENT_OF_HALF_TV_ANGLE; + } else if (uiMode == Configuration.UI_MODE_TYPE_WATCH) { + // Watch + return ESTIMATED_VIEWING_DISTANCE_FOR_WATCH; + } else { + // Tablet, phone, etc + return AVERAGE_VIEWING_DISTANCE_FOR_PHONES; + } + } + + /** + * Calculates the duration scaling factor of an animation based on the hint that the animation + * will move across the entire screen. A scaling factor of 1 means the duration on this given + * device will be the same as the duration set through + * {@link android.animation.Animator#setDuration(long)}. The calculation uses Nexus 5 as a + * baseline device. That is, the duration of the animation on a given device will scale its + * duration so that it has the same look and feel as the animation on Nexus 5. In order to + * achieve the same perceived effect of the animation across different devices, we maintain + * the same angular speed of the same animation in users' field of view. Therefore, the + * duration scale factor is determined by the ratio of the angular movement on current + * devices to that on the baseline device. + * + * @param width width of the screen (in inches) + * @param height height of the screen (in inches) + * @param viewingDistance the viewing distance of the device (i.e. watch, phone, TV, etc) in + * inches + * @return scaling factor (or multiplier) of the duration set through + * {@link android.animation.Animator#setDuration(long)} on current device. + * @hide + */ + public static float getScreenSizeBasedDurationScale(float width, float height, + float viewingDistance) { + // Animation's moving distance is proportional to the screen size. + float diagonal = (float) Math.sqrt(width * width + height * height); + float diagonalViewAngle = (float) Math.toDegrees(Math.atan((diagonal / 2f) + / viewingDistance) * 2); + return diagonalViewAngle / N5_DIAGONAL_VIEW_ANGLE; + } + + /** + * Calculates the duration scaling factor of an animation under the assumption that the + * animation is defined to move the same amount of distance (in dp) across all devices. A + * scaling factor of 1 means the duration on this given device will be the same as the + * duration set through {@link android.animation.Animator#setDuration(long)}. The calculation + * uses Nexus 5 as a baseline device. That is, the duration of the animation on a given + * device will scale its duration so that it has the same look and feel as the animation on + * Nexus 5. In order to achieve the same perceived effect of the animation across different + * devices, we maintain the same angular velocity of the same animation in users' field of + * view. Therefore, the duration scale factor is determined by the ratio of the angular + * movement on current devices to that on the baseline device. + * + * @param density logical density of the display. {@link android.util.DisplayMetrics#density} + * @param dpi pixels per inch + * @param viewingDistance viewing distance of the device (in inches) + * @return the scaling factor of duration + * @hide + */ + public static float getDpBasedDurationScale(float density, float dpi, + float viewingDistance) { + // Angle in users' field of view per dp: + float anglePerDp = (float) Math.atan2((density / dpi) / 2, viewingDistance) * 2; + float baselineAnglePerDp = (float) Math.atan2((N5_DENSITY / N5_DPI) / 2, + AVERAGE_VIEWING_DISTANCE_FOR_PHONES) * 2; + return anglePerDp / baselineAnglePerDp; + } } diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index a157087..06a5bd2 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -534,22 +534,23 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { @Override public void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; + final SavedState ss = (SavedState) state; // TODO: Move instance state into DayPickerView, YearPickerView. mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); - mCurrentView = ss.getCurrentView(); mMinDate.setTimeInMillis(ss.getMinDate()); mMaxDate.setTimeInMillis(ss.getMaxDate()); onCurrentDateChanged(false); - setCurrentView(mCurrentView); + + final int currentView = ss.getCurrentView(); + setCurrentView(currentView); final int listPosition = ss.getListPosition(); if (listPosition != -1) { - if (mCurrentView == VIEW_MONTH_DAY) { + if (currentView == VIEW_MONTH_DAY) { mDayPickerView.setCurrentItem(listPosition); - } else if (mCurrentView == VIEW_YEAR) { + } else if (currentView == VIEW_YEAR) { final int listPositionOffset = ss.getListPositionOffset(); mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset); } @@ -601,7 +602,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { * Class for managing state storing/restoring. */ private static class SavedState extends View.BaseSavedState { - private final int mSelectedYear; private final int mSelectedMonth; private final int mSelectedDay; diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index ec2528f..0e0b2d3 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -22,9 +22,12 @@ import com.android.internal.R; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.MathUtils; +import android.view.View; +import java.util.ArrayList; import java.util.Calendar; import java.util.Locale; @@ -41,6 +44,8 @@ class DayPickerView extends ViewPager { private final Calendar mMinDate = Calendar.getInstance(); private final Calendar mMaxDate = Calendar.getInstance(); + private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1); + private final DayPickerAdapter mAdapter; /** Temporary calendar used for date calculations. */ @@ -140,6 +145,93 @@ class DayPickerView extends ViewPager { }); } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + populate(); + + // Everything below is mostly copied from FrameLayout. + int count = getChildCount(); + + final boolean measureMatchParentChildren = + MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || + MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; + + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + measureChild(child, widthMeasureSpec, heightMeasureSpec); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + maxWidth = Math.max(maxWidth, child.getMeasuredWidth()); + maxHeight = Math.max(maxHeight, child.getMeasuredHeight()); + childState = combineMeasuredStates(childState, child.getMeasuredState()); + if (measureMatchParentChildren) { + if (lp.width == LayoutParams.MATCH_PARENT || + lp.height == LayoutParams.MATCH_PARENT) { + mMatchParentChildren.add(child); + } + } + } + } + + // Account for padding too + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + // Check against our foreground's minimum height and width + final Drawable drawable = getForeground(); + if (drawable != null) { + maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); + maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); + } + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + + count = mMatchParentChildren.size(); + if (count > 1) { + for (int i = 0; i < count; i++) { + final View child = mMatchParentChildren.get(i); + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int childWidthMeasureSpec; + final int childHeightMeasureSpec; + + if (lp.width == LayoutParams.MATCH_PARENT) { + childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), + MeasureSpec.EXACTLY); + } else { + childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + getPaddingLeft() + getPaddingRight(), + lp.width); + } + + if (lp.height == LayoutParams.MATCH_PARENT) { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( + getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), + MeasureSpec.EXACTLY); + } else { + childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, + getPaddingTop() + getPaddingBottom(), + lp.height); + } + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + } + + mMatchParentChildren.clear(); + } + public void setDayOfWeekTextAppearance(int resId) { mAdapter.setDayOfWeekTextAppearance(resId); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 491826a..29073be 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -27,6 +27,7 @@ import android.content.UndoManager; import android.content.UndoOperation; import android.content.UndoOwner; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; @@ -109,6 +110,7 @@ import java.text.BreakIterator; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.List; /** * Helper class used by TextView to handle editable text views. @@ -2980,6 +2982,8 @@ public class Editor { } } + addIntentMenuItemsForTextProcessing(menu); + if (menu.hasVisibleItems() || mode.getCustomView() != null) { mTextView.setHasTransientState(true); return true; @@ -3036,6 +3040,32 @@ public class Editor { styledAttributes.recycle(); } + private void addIntentMenuItemsForTextProcessing(Menu menu) { + if (mTextView.canProcessText()) { + PackageManager packageManager = mTextView.getContext().getPackageManager(); + List<ResolveInfo> supportedActivities = + packageManager.queryIntentActivities(createProcessTextIntent(), 0); + for (ResolveInfo info : supportedActivities) { + menu.add(info.loadLabel(packageManager)) + .setIntent(createProcessTextIntentForResolveInfo(info)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + } + } + + private Intent createProcessTextIntent() { + return new Intent() + .setAction(Intent.ACTION_PROCESS_TEXT) + .setType("text/plain"); + } + + private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) { + return createProcessTextIntent() + .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, !mTextView.isTextEditable()) + .setClassName(info.activityInfo.packageName, info.activityInfo.name); + } + @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateReplaceItem(menu); @@ -3060,6 +3090,13 @@ public class Editor { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (item.getIntent() != null + && item.getIntent().getAction().equals(Intent.ACTION_PROCESS_TEXT)) { + item.getIntent().putExtra(Intent.EXTRA_PROCESS_TEXT, mTextView.getSelectedText()); + mTextView.startActivityForResult( + item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE); + return true; + } if (mCustomSelectionActionModeCallback != null && mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) { return true; diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 205d35e..24e9cbe 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -60,7 +60,6 @@ import android.widget.RemoteViews.RemoteView; import java.util.ArrayList; - /** * <p> * Visual indicator of progress in some operation. Displays a bar to the user @@ -266,9 +265,14 @@ public class ProgressBar extends View { final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (progressDrawable != null) { - // Calling this method can set mMaxHeight, make sure the corresponding - // XML attribute for mMaxHeight is read after calling this method - setProgressDrawableTiled(progressDrawable); + // Calling setProgressDrawable can set mMaxHeight, so make sure the + // corresponding XML attribute for mMaxHeight is read after calling + // this method. + if (needsTileify(progressDrawable)) { + setProgressDrawableTiled(progressDrawable); + } else { + setProgressDrawable(progressDrawable); + } } @@ -292,13 +296,17 @@ public class ProgressBar extends View { setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress)); - setSecondaryProgress( - a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); + setSecondaryProgress(a.getInt( + R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); final Drawable indeterminateDrawable = a.getDrawable( R.styleable.ProgressBar_indeterminateDrawable); if (indeterminateDrawable != null) { - setIndeterminateDrawableTiled(indeterminateDrawable); + if (needsTileify(indeterminateDrawable)) { + setIndeterminateDrawableTiled(indeterminateDrawable); + } else { + setIndeterminateDrawable(indeterminateDrawable); + } } mOnlyIndeterminate = a.getBoolean( @@ -395,6 +403,45 @@ public class ProgressBar extends View { } /** + * Returns {@code true} if the target drawable needs to be tileified. + * + * @param dr the drawable to check + * @return {@code true} if the target drawable needs to be tileified, + * {@code false} otherwise + */ + private static boolean needsTileify(Drawable dr) { + if (dr instanceof LayerDrawable) { + final LayerDrawable orig = (LayerDrawable) dr; + final int N = orig.getNumberOfLayers(); + for (int i = 0; i < N; i++) { + if (needsTileify(orig.getDrawable(i))) { + return true; + } + } + return false; + } + + if (dr instanceof StateListDrawable) { + final StateListDrawable in = (StateListDrawable) dr; + final int N = in.getStateCount(); + for (int i = 0; i < N; i++) { + if (needsTileify(in.getStateDrawable(i))) { + return true; + } + } + return false; + } + + // If there's a bitmap that's not wrapped with a ClipDrawable or + // ScaleDrawable, we'll need to wrap it and apply tiling. + if (dr instanceof BitmapDrawable) { + return true; + } + + return false; + } + + /** * Converts a drawable to a tiled version of itself. It will recursively * traverse layer and state list drawables. */ @@ -448,18 +495,14 @@ public class ProgressBar extends View { mSampleTile = tileBitmap; } - final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); - final BitmapShader bitmapShader = new BitmapShader(tileBitmap, - Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); - shapeDrawable.getPaint().setShader(bitmapShader); + final BitmapDrawable clone = (BitmapDrawable) bitmap.getConstantState().newDrawable(); + clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); - // Ensure the tint and filter are propagated in the correct order. - shapeDrawable.setTintList(bitmap.getTint()); - shapeDrawable.setTintMode(bitmap.getTintMode()); - shapeDrawable.setColorFilter(bitmap.getColorFilter()); - - return clip ? new ClipDrawable( - shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable; + if (clip) { + return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL); + } else { + return clone; + } } return drawable; diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index d9f1f0e..aa7f0b6 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -585,7 +585,6 @@ class SimpleMonthView extends View { mToday = day; } } - mNumWeeks = calculateNumRows(); // Invalidate the old title. mTitle = null; @@ -616,18 +615,6 @@ class SimpleMonthView extends View { } } - public void reuse() { - mNumWeeks = MAX_WEEKS_IN_MONTH; - requestLayout(); - } - - private int calculateNumRows() { - final int offset = findDayOffset(); - final int dividend = (offset + mDaysInMonth) / DAYS_IN_WEEK; - final int remainder = (offset + mDaysInMonth) % DAYS_IN_WEEK; - return dividend + (remainder > 0 ? 1 : 0); - } - private boolean sameDay(int day, Calendar today) { return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH) && day == today.get(Calendar.DAY_OF_MONTH); @@ -635,8 +622,9 @@ class SimpleMonthView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int preferredHeight = mDesiredDayHeight * mNumWeeks + mDesiredDayOfWeekHeight - + mDesiredMonthHeight + getPaddingTop() + getPaddingBottom(); + final int preferredHeight = mDesiredDayHeight * MAX_WEEKS_IN_MONTH + + mDesiredDayOfWeekHeight + mDesiredMonthHeight + + getPaddingTop() + getPaddingBottom(); final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK + getPaddingStart() + getPaddingEnd(); final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 11439e4..9bbf375 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -27,6 +27,7 @@ import android.annotation.XmlRes; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.Intent; import android.content.UndoManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; @@ -289,6 +290,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // System wide time for last cut or copy action. static long LAST_CUT_OR_COPY_TIME; + /** + * @hide + */ + static final int PROCESS_TEXT_REQUEST_CODE = 100; + private ColorStateList mTextColor; private ColorStateList mHintTextColor; private ColorStateList mLinkTextColor; @@ -534,6 +540,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout mLayout; private boolean mLocaleChanged = false; + @ViewDebug.ExportedProperty(category = "text") private int mGravity = Gravity.TOP | Gravity.START; private boolean mHorizontallyScrolling; @@ -1414,6 +1421,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** + * @hide + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PROCESS_TEXT_REQUEST_CODE) { + CharSequence result = data != null + ? data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT) + : ""; + if (isTextEditable()) { + replaceSelectionWithText(result); + } else { + Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG).show(); + } + } + } + private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) { Typeface tf = null; if (familyName != null) { @@ -7474,6 +7498,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return selectionStart >= 0 && selectionStart != selectionEnd; } + String getSelectedText() { + if (hasSelection()) { + return String.valueOf(mText.subSequence(getSelectionStart(), getSelectionEnd())); + } + return null; + } + /** * Sets the properties of this field (lines, horizontally scrolling, * transformation method) to be for a single-line input. @@ -9066,6 +9097,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hasPrimaryClip()); } + boolean canProcessText() { + if (!getContext().canStartActivityForResult() || getId() == View.NO_ID + || hasPasswordTransformationMethod()) { + return false; + } + + if (mText.length() > 0 && hasSelection() && mEditor != null) { + return true; + } + + return false; + } + boolean selectAllText() { // Need to hide insert point cursor controller before settings selection, otherwise insert // point cursor controller obtains cursor update event and update cursor with cancelling @@ -9078,6 +9122,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return length > 0; } + void replaceSelectionWithText(CharSequence text) { + ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); + } + /** * Paste clipboard content between min and max positions. */ diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java index 7182414..89e59f9 100644 --- a/core/java/android/widget/YearPickerView.java +++ b/core/java/android/widget/YearPickerView.java @@ -178,24 +178,29 @@ class YearPickerView extends ListView { @Override public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); + final TextView v; + final boolean hasNewView = convertView == null; + if (hasNewView) { + v = (TextView) mInflater.inflate(ITEM_LAYOUT, parent, false); + } else { + v = (TextView) convertView; } final int year = getYearForPosition(position); final boolean activated = mActivatedYear == year; - final int textAppearanceResId; - if (activated && ITEM_TEXT_ACTIVATED_APPEARANCE != 0) { - textAppearanceResId = ITEM_TEXT_ACTIVATED_APPEARANCE; - } else { - textAppearanceResId = ITEM_TEXT_APPEARANCE; + if (hasNewView || v.isActivated() != activated) { + final int textAppearanceResId; + if (activated && ITEM_TEXT_ACTIVATED_APPEARANCE != 0) { + textAppearanceResId = ITEM_TEXT_ACTIVATED_APPEARANCE; + } else { + textAppearanceResId = ITEM_TEXT_APPEARANCE; + } + v.setTextAppearance(textAppearanceResId); + v.setActivated(activated); } - final TextView v = (TextView) convertView; - v.setText("" + year); - v.setTextAppearance(v.getContext(), textAppearanceResId); - v.setActivated(activated); + v.setText(Integer.toString(year)); return v; } diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 9dabb4e..b8110e3 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -170,9 +170,8 @@ public class AlertController { } private static boolean shouldCenterSingleButton(Context context) { - TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogCenterButtons, - outValue, true); + final TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.alertDialogCenterButtons, outValue, true); return outValue.data != 0; } @@ -182,27 +181,25 @@ public class AlertController { mWindow = window; mHandler = new ButtonHandler(di); - TypedArray a = context.obtainStyledAttributes(null, - com.android.internal.R.styleable.AlertDialog, - com.android.internal.R.attr.alertDialogStyle, 0); + final TypedArray a = context.obtainStyledAttributes(null, + R.styleable.AlertDialog, R.attr.alertDialogStyle, 0); - mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout, - com.android.internal.R.layout.alert_dialog); + mAlertDialogLayout = a.getResourceId( + R.styleable.AlertDialog_layout, R.layout.alert_dialog); mButtonPanelSideLayout = a.getResourceId( - com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0); - + R.styleable.AlertDialog_buttonPanelSideLayout, 0); mListLayout = a.getResourceId( - com.android.internal.R.styleable.AlertDialog_listLayout, - com.android.internal.R.layout.select_dialog); + R.styleable.AlertDialog_listLayout, R.layout.select_dialog); + mMultiChoiceItemLayout = a.getResourceId( - com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout, - com.android.internal.R.layout.select_dialog_multichoice); + R.styleable.AlertDialog_multiChoiceItemLayout, + R.layout.select_dialog_multichoice); mSingleChoiceItemLayout = a.getResourceId( - com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout, - com.android.internal.R.layout.select_dialog_singlechoice); + R.styleable.AlertDialog_singleChoiceItemLayout, + R.layout.select_dialog_singlechoice); mListItemLayout = a.getResourceId( - com.android.internal.R.styleable.AlertDialog_listItemLayout, - com.android.internal.R.layout.select_dialog_item); + R.styleable.AlertDialog_listItemLayout, + R.layout.select_dialog_item); a.recycle(); } @@ -1067,9 +1064,9 @@ public class AlertController { } private void createListView(final AlertController dialog) { - final RecycleListView listView = (RecycleListView) - mInflater.inflate(dialog.mListLayout, null); - ListAdapter adapter; + final RecycleListView listView = + (RecycleListView) mInflater.inflate(dialog.mListLayout, null); + final ListAdapter adapter; if (mIsMultiChoice) { if (mCursor == null) { @@ -1115,14 +1112,20 @@ public class AlertController { }; } } else { - int layout = mIsSingleChoice - ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout; - if (mCursor == null) { - adapter = (mAdapter != null) ? mAdapter - : new CheckedItemAdapter(mContext, layout, R.id.text1, mItems); + final int layout; + if (mIsSingleChoice) { + layout = dialog.mSingleChoiceItemLayout; + } else { + layout = dialog.mListItemLayout; + } + + if (mCursor != null) { + adapter = new SimpleCursorAdapter(mContext, layout, mCursor, + new String[] { mLabelColumn }, new int[] { R.id.text1 }); + } else if (mAdapter != null) { + adapter = mAdapter; } else { - adapter = new SimpleCursorAdapter(mContext, layout, - mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1}); + adapter = new CheckedItemAdapter(mContext, layout, R.id.text1, mItems); } } diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java index ee225a1..6aa81ce 100644 --- a/core/java/com/android/internal/logging/MetricsConstants.java +++ b/core/java/com/android/internal/logging/MetricsConstants.java @@ -69,7 +69,9 @@ public interface MetricsConstants { public static final int DEVELOPMENT = 39; public static final int DEVICEINFO = 40; public static final int DEVICEINFO_IMEI_INFORMATION = 41; + @Deprecated public static final int DEVICEINFO_MEMORY = 42; + public static final int DEVICEINFO_STORAGE = 42; public static final int DEVICEINFO_SIM_STATUS = 43; public static final int DEVICEINFO_STATUS = 44; public static final int DEVICEINFO_USB = 45; diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 024b7c5..59dbec6 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.SensorManager; import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.BatteryStats.Uid; import android.os.Bundle; @@ -130,16 +129,11 @@ public final class BatteryStatsHelper { return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); } - public static boolean checkHasWifiPowerReporting(Context context, PowerProfile profile) { - WifiManager manager = context.getSystemService(WifiManager.class); - if (manager.isEnhancedPowerReportingSupported()) { - if (profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 && - profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 && - profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0) { - return true; - } - } - return false; + public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) { + return stats.hasWifiActivityReporting() && + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 && + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 && + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0; } public BatteryStatsHelper(Context context) { @@ -339,7 +333,7 @@ public final class BatteryStatsHelper { mMobileRadioPowerCalculator.reset(mStats); if (mWifiPowerCalculator == null) { - if (checkHasWifiPowerReporting(mContext, mPowerProfile)) { + if (checkHasWifiPowerReporting(mStats, mPowerProfile)) { mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile); } else { mWifiPowerCalculator = new WifiPowerEstimator(mPowerProfile); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index c5c0ba6..fbb2dfc 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -472,6 +472,8 @@ public final class BatteryStatsImpl extends BatteryStats { private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry(); private PowerProfile mPowerProfile; + private boolean mHasWifiEnergyReporting = false; + private boolean mHasBluetoothEnergyReporting = false; /* * Holds a SamplingTimer associated with each kernel wakelock name being tracked. @@ -4298,6 +4300,10 @@ public final class BatteryStatsImpl extends BatteryStats { return mBluetoothStateTimer[bluetoothState].getCountLocked(which); } + @Override public boolean hasBluetoothActivityReporting() { + return mHasBluetoothEnergyReporting; + } + @Override public long getBluetoothControllerActivity(int type, int which) { if (type >= 0 && type < mBluetoothActivityCounters.length) { return mBluetoothActivityCounters[type].getCountLocked(which); @@ -4305,6 +4311,10 @@ public final class BatteryStatsImpl extends BatteryStats { return 0; } + @Override public boolean hasWifiActivityReporting() { + return mHasWifiEnergyReporting; + } + @Override public long getWifiControllerActivity(int type, int which) { if (type >= 0 && type < mWifiActivityCounters.length) { return mWifiActivityCounters[type].getCountLocked(which); @@ -7567,6 +7577,8 @@ public final class BatteryStatsImpl extends BatteryStats { } if (info != null) { + mHasWifiEnergyReporting = true; + // Measured in mAms final long txTimeMs = info.getControllerTxTimeMillis(); final long rxTimeMs = info.getControllerRxTimeMillis(); @@ -7778,6 +7790,7 @@ public final class BatteryStatsImpl extends BatteryStats { */ public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) { if (info != null && mOnBatteryInternal && false) { + mHasBluetoothEnergyReporting = true; mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked( info.getControllerRxTimeMillis()); mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked( @@ -9533,6 +9546,8 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); } + mHasWifiEnergyReporting = in.readInt() != 0; + mHasBluetoothEnergyReporting = in.readInt() != 0; mNumConnectivityChange = in.readInt(); mLoadedNumConnectivityChange = in.readInt(); mUnpluggedNumConnectivityChange = in.readInt(); @@ -9686,6 +9701,8 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mWifiActivityCounters[i].writeToParcel(out); } + out.writeInt(mHasWifiEnergyReporting ? 1 : 0); + out.writeInt(mHasBluetoothEnergyReporting ? 1 : 0); out.writeInt(mNumConnectivityChange); out.writeInt(mLoadedNumConnectivityChange); out.writeInt(mUnpluggedNumConnectivityChange); diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 4e77f6b..4fb8b55 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -16,12 +16,15 @@ package com.android.internal.os; import android.os.BatteryStats; +import android.util.Log; /** * WiFi power calculator for when BatteryStats supports energy reporting * from the WiFi controller. */ public class WifiPowerCalculator extends PowerCalculator { + private static final boolean DEBUG = BatteryStatsHelper.DEBUG; + private static final String TAG = "WifiPowerCalculator"; private final double mIdleCurrentMa; private final double mTxCurrentMa; private final double mRxCurrentMa; @@ -75,6 +78,10 @@ public class WifiPowerCalculator extends PowerCalculator { + (rxTimeMs * mRxCurrentMa)) / (1000*60*60); } app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain); + + if (DEBUG) { + Log.d(TAG, "left over WiFi power: " + BatteryStatsHelper.makemAh(app.wifiPowerMah)); + } } @Override diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index f908fcb..62e724a 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -16,6 +16,8 @@ package com.android.internal.util; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.ArraySet; import dalvik.system.VMRuntime; @@ -24,13 +26,13 @@ import libcore.util.EmptyArray; import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Objects; /** * ArrayUtils contains some methods that you can call to find out * the most efficient increments by which to grow arrays. */ -public class ArrayUtils -{ +public class ArrayUtils { private static final int CACHE_SIZE = 73; private static Object[] sCache = new Object[CACHE_SIZE]; @@ -158,11 +160,7 @@ public class ArrayUtils public static <T> int indexOf(T[] array, T value) { if (array == null) return -1; for (int i = 0; i < array.length; i++) { - if (array[i] == null) { - if (value == null) return i; - } else { - if (value != null && array[i].equals(value)) return i; - } + if (Objects.equals(array[i], value)) return i; } return -1; } @@ -209,17 +207,15 @@ public class ArrayUtils } /** - * Appends an element to a copy of the array and returns the copy. - * @param array The original array, or null to represent an empty array. - * @param element The element to add. - * @return A new array that contains all of the elements of the original array - * with the specified element added at the end. + * Adds value to given array if not already present, providing set-like + * behavior. */ @SuppressWarnings("unchecked") - public static <T> T[] appendElement(Class<T> kind, T[] array, T element) { + public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) { final T[] result; final int end; if (array != null) { + if (contains(array, element)) return array; end = array.length; result = (T[])Array.newInstance(kind, end + 1); System.arraycopy(array, 0, result, 0, end); @@ -232,21 +228,15 @@ public class ArrayUtils } /** - * Removes an element from a copy of the array and returns the copy. - * If the element is not present, then the original array is returned unmodified. - * @param array The original array, or null to represent an empty array. - * @param element The element to remove. - * @return A new array that contains all of the elements of the original array - * except the first copy of the specified element removed. If the specified element - * was not present, then returns the original array. Returns null if the result - * would be an empty array. + * Removes value from given array if present, providing set-like behavior. */ @SuppressWarnings("unchecked") - public static <T> T[] removeElement(Class<T> kind, T[] array, T element) { + public static @Nullable <T> T[] removeElement(Class<T> kind, @Nullable T[] array, T element) { if (array != null) { + if (!contains(array, element)) return array; final int length = array.length; for (int i = 0; i < length; i++) { - if (array[i] == element) { + if (Objects.equals(array[i], element)) { if (length == 1) { return null; } @@ -261,14 +251,10 @@ public class ArrayUtils } /** - * Appends a new value to a copy of the array and returns the copy. If - * the value is already present, the original array is returned - * @param cur The original array, or null to represent an empty array. - * @param val The value to add. - * @return A new array that contains all of the values of the original array - * with the new value added, or the original array. + * Adds value to given array if not already present, providing set-like + * behavior. */ - public static int[] appendInt(int[] cur, int val) { + public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { if (cur == null) { return new int[] { val }; } @@ -284,7 +270,10 @@ public class ArrayUtils return ret; } - public static int[] removeInt(int[] cur, int val) { + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable int[] removeInt(@Nullable int[] cur, int val) { if (cur == null) { return null; } @@ -305,14 +294,10 @@ public class ArrayUtils } /** - * Appends a new value to a copy of the array and returns the copy. If - * the value is already present, the original array is returned - * @param cur The original array, or null to represent an empty array. - * @param val The value to add. - * @return A new array that contains all of the values of the original array - * with the new value added, or the original array. + * Adds value to given array if not already present, providing set-like + * behavior. */ - public static long[] appendLong(long[] cur, long val) { + public static @NonNull long[] appendLong(@Nullable long[] cur, long val) { if (cur == null) { return new long[] { val }; } @@ -328,7 +313,10 @@ public class ArrayUtils return ret; } - public static long[] removeLong(long[] cur, long val) { + /** + * Removes value from given array if present, providing set-like behavior. + */ + public static @Nullable long[] removeLong(@Nullable long[] cur, long val) { if (cur == null) { return null; } diff --git a/core/java/com/android/internal/widget/DialogViewAnimator.java b/core/java/com/android/internal/widget/DialogViewAnimator.java new file mode 100644 index 0000000..bdfc1af --- /dev/null +++ b/core/java/com/android/internal/widget/DialogViewAnimator.java @@ -0,0 +1,141 @@ +/* + * 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.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ViewAnimator; + +import java.util.ArrayList; + +/** + * ViewAnimator with a more reasonable handling of MATCH_PARENT. + */ +public class DialogViewAnimator extends ViewAnimator { + private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1); + + public DialogViewAnimator(Context context) { + super(context); + } + + public DialogViewAnimator(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final boolean measureMatchParentChildren = + MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || + MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; + + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + // First measure all children and record maximum dimensions where the + // spec isn't MATCH_PARENT. + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (getMeasureAllChildren() || child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final boolean matchWidth = lp.width == LayoutParams.MATCH_PARENT; + final boolean matchHeight = lp.height == LayoutParams.MATCH_PARENT; + if (measureMatchParentChildren && (matchWidth || matchHeight)) { + mMatchParentChildren.add(child); + } + + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + + // Measured dimensions only count against the maximum + // dimensions if they're not MATCH_PARENT. + int state = 0; + + if (measureMatchParentChildren && !matchWidth) { + maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + + lp.leftMargin + lp.rightMargin); + state |= child.getMeasuredWidthAndState() & MEASURED_STATE_MASK; + } + + if (measureMatchParentChildren && !matchHeight) { + maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + + lp.topMargin + lp.bottomMargin); + state |= (child.getMeasuredHeightAndState() >> MEASURED_HEIGHT_STATE_SHIFT) + & (MEASURED_STATE_MASK >> MEASURED_HEIGHT_STATE_SHIFT); + } + + childState = combineMeasuredStates(childState, state); + } + } + + // Account for padding too. + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height and width. + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + // Check against our foreground's minimum height and width. + final Drawable drawable = getForeground(); + if (drawable != null) { + maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); + maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); + } + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + + // Measure remaining MATCH_PARENT children again using real dimensions. + final int matchCount = mMatchParentChildren.size(); + for (int i = 0; i < matchCount; i++) { + final View child = mMatchParentChildren.get(i); + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + final int childWidthMeasureSpec; + if (lp.width == LayoutParams.MATCH_PARENT) { + childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + getMeasuredWidth() - getPaddingLeft() - getPaddingRight() + - lp.leftMargin - lp.rightMargin, + MeasureSpec.EXACTLY); + } else { + childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, + lp.width); + } + + final int childHeightMeasureSpec; + if (lp.height == LayoutParams.MATCH_PARENT) { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( + getMeasuredHeight() - getPaddingTop() - getPaddingBottom() + - lp.topMargin - lp.bottomMargin, + MeasureSpec.EXACTLY); + } else { + childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, + getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, + lp.height); + } + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + mMatchParentChildren.clear(); + } +} diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java index 8d66191..5c08daf 100644 --- a/core/java/com/android/internal/widget/ViewPager.java +++ b/core/java/com/android/internal/widget/ViewPager.java @@ -889,7 +889,7 @@ public class ViewPager extends ViewGroup { } } - void populate() { + public void populate() { populate(mCurItem); } |
